Breaking Dependencies

Recently I got a comment asking the following question about dependencies:

Could you explain how you “separate dependencies so they could be compiled independently”… [for]… faster compilation and execution?

Handcuffs and Keyboard

This is a great question.  Sometimes our code is architected in a way that has too many dependencies.  This makes the code very bloated.  Everything has to be compiled and run together in one big monolithic chunk.  Taking the time to break out dependencies costs time up front, but it is a great investment in your code infrastructure.  Quicker builds and faster execution can save minutes every single time you compile and run your code.  If you build 10/20/50 times a day, these savings add up a whole lot over time.

The techniques I discuss in this post will come from Working Effectively with Legacy Code, a fantastic book on how to work with old code that is not tested well.  I highly encourage you to get this book if you work with a big codebase on a day-to-day basis that needs to be refactored.

Let’s look into dependency-breaking techniques!

Seams Allow Us to Remove Dependencies

A seam is a concept I learned about from the Working Effectively with Legacy Code book.  It is a place in code where you can change the behavior of your code.  Each seam has an “enabling point”, where you use some type of logic to determine to use one behavior or another behavior.

Pants Seam

When you can change behavior, you can completely remove a dependency.  Rather than one piece of code that includes more code that includes more code… ad nauseam… you can replace that code with something that does very little.

In the production code, you have all those dependencies.  But with the alternate behavior, you can make it do something very different – maybe nothing.  This means you can break your code up into separate “packages”.  This allows you to compile and execute those small packages stand-alone.  You can develop much quicker in that package until you are happy with your change.  Then you can integrate that package back into the bigger production code and do a little bit of integration.

As a really simple example, suppose we have the following pseudocode to get a stock’s price.

Let’s pretend that “getJsonFromStockWebsite” and “parseJson” both require 30 lines of code to complete the task.   Imagine they were originally inside this getStock function.  Also imagine that those 30 lines of code require 5 new dependencies (as discussed earlier about a dependency that requires another dependency and so on).

As a first step to getting rid of dependencies, we moved those 30 lines of code outside of the “getStock” function and into these two functions.

In his book, Feathers identifies three different types of seams we can use to change behavior.

  1. Preprocessor seams (for C/C++)
  2. Link seams
  3. Object seams

Let’s look at what each of these might look like with our above example.

Preprocessor seams

With this code, we just compile something differently for PROD and TEST preprocessor defines.  In your production code that has everything, you define the PROD flag.  In the smaller package where you don’t care as much about the network access to getJson data, you define the TEST flag and return some hard-coded constant (or some other simpler logic).

This allows you to develop this code within a smaller package that has fewer dependencies.

Link seams

With the two functions above (the PROD vs. TEST sections), we can also put these in separate files.  Perhaps we have a StockNetworkAccess.h and a TestStockNetworkAccess.h files (with appropriate .c or .cpp files).  You can then change your makefiles (or build scripts of whatever language you are using) to build one file or the other.  So when you are linking your code together, you change the behavior based on which file you build.

Object seams

We can declare the getJsonFromStockWebsite to be a virtual function.  We then can implement it different ways in child classes.  The child class that gets used will be different in the production code vs. the packaged/test code.  This takes advantage of polymorphism and interface classes to determine the behavior of that function call.

Conclusion

I did not go into a whole lot of detail in this post about breaking dependencies.  That’s because there are so many ways to do it, and some of them can be complex.  I could write many, many posts on this topic and still only scratch the surface.

I highly encourage anyone interested in this topic, particularly if you work with legacy code a lot of the time, to read Working Effectively with Legacy Code.  It goes much more into depth with this topic and many more dependency breaking techniques with examples.

When I worked at one of my previous companies, we actually had a book club where we talked about concepts in that book.  It is eye-opening.  It is a great look into working with hard-to-test code and breaking up those dependencies (with minimal code changes) so you can test the code before changing it.  That book will change the way you work and think about code.  It’s a great read.

Please follow and like us:
error

Leave a Reply

avatar
  Subscribe  
Notify of