What if I told you change is coming? Lots of it. Like the butterfly effect, it can cause your code to break in unexpected ways. Or, like the domino effect, a single code change could trigger a chain reaction that forces you to change many additional bits. A stable codebase is needed to minimise the propagation of this coming change. Let's explore how the Stable Dependencies Principle of Uncle Bob can guide us towards it.
Stability is determined by the likelihood a bit of code has to change. The less likely it’s to change, the more stable it is. And vice versa. Consider modules (libraries, components, or packages). There are many reasons why some may change more than others. Risky modules change less than low-risk ones. Expensive modules change less than cheap ones. Or users may simply request fewer changes to some than others. We need to figure out how our modules should depend on each other to optimise stability.
The Stable Dependencies Principle says modules may not depend on less stable ones. Consider two modules, A and B. Suppose A is likely to change 𝓧 times. And B is likely to change 𝓧/2 times. A may depend on B, as it's more stable (less likely to change). Now. Let's explore what happens if B is less stable. Say 2𝓧 as likely to change. If A depends on B, it could be twice as likely to change than it was alone. Because every time B changes, A may also have to. A violation of the principle. How can we adhere to it?
If A depends on a less stable B, we should remove that coupling. As our mantra reminds us, it shouldn't know about it. We could do it by refactoring the code so A doesn't have to call B. Problem solved. But what if A must call B? We can’t then refactor that flow of control away. But we can invert the source-code dependency between these modules such that it points against the flow of control. This technique is called dependency inversion. This means A can call B at runtime without knowing about it in the source code. Brilliant! Let's use it to solve our problem.
Suppose module A has a class, Rules, that calls a class, Data, in module B.
We can create an interface, IData, in A to represent Data. Let Rules call IData. And let Data implement IData. We'll then configure an Inversion of Control (IoC) framework – such as .NET and Nest – to inject Data, at runtime, where IData is used.
Voilà! A doesn't know about B anymore.
Let’s bring it together. The Stable Dependencies Principle reduces the propagation of change. Dependency inversion can help us adhere to it. The lesser the propagation of changes the faster we’ll be able to make them. This increase in productivity lowers the cost of change. And enables us to use the time that would otherwise be used on changing superfluously affected code to develop new features. This is the way.