I never thought that I would find myself blogging on the topic of SOLID Principles. Not that I think it is unimportant, because it certainly is an important topic, it is just that it is so easy to find books, blogs and other written material on the topic already.
On the other hand, people often ask me if it requires a special coding style if you want to implement Continuous Delivery. And I often reply that, no you are not forced to use any specific coding style. But it goes without saying that you need to deliver your code continuously. And in the spirit of Continuous Delivery you need to minimize the risk of each delivery.
Which means that you need to do small (baby-step), additive and non-breaking deliveries.
If you are in the habit of checking in several times a day and you are confident that your code is delivered to the Live environment at the end of each day, or several times a day, then you are good to go and you may not need to read any further.
On the other hand, if you either find it impossible to chop up your implementation into small and additive pieces, you should read on to learn my take on the SOLID principles.
And if you are then still not confident that following the SOLID principles will help you ensure that each of your check-ins can potentially be released any time of day, you will want to read my future blog posts on coding with a safety net and on patterns that fit nicely into all this.
The SOLID Principles
I will not go into details of the SOLID principles but rather sketch how I believe they interrelate in order to make my point. I do not strictly follow Uncle Bob’s original explanation regarding what principle(s) follow by rigorously applying what other principle(s), so you may find my points a bit controversial.
First of all, we need to think about responsibilities in all coding, hence the S – Single Responsibility (SR) – meaning that we want to put code that will change for the same reason together. This means that huge classes or methods are out of the question as these will almost certainly contain multiple responsibilities. One point that many well-meaning developers often overlook is that it is also poor practice to split the code into too small units. Splitting a class with a well-defined and coherent responsibility into several smaller classes will only give you more classes to manage – and the unit tests will be difficult to understand and maintain.
Secondly, we need to think hard about the way we allow our responsibilities to depend on each other. While it might seem obvious that high-level components must depend on lower level components, this kind of code structure tends to create highly coupled systems that are difficult to maintain. The letter D in the SOLID Principles – Dependency Inversion (DI) – states that high level modules must not depend on lover level modules, but rather they should depend on abstractions which do not depend on details.
In day-to-day work I usually state that Dependency Inversion essentially means that a class with a given responsibility must assume that its dependencies are injected into it without the class implementing any of the mechanics needed to get or dispose of the right instances at the right time. These mechanics must be implemented in a single place, the Composition Root, in the program. This day-to-day view is not exactly Uncle Bob’s original thoughts, and also ties into Dependency Injection which is not necessary in order to follow the DI principle. But using Dependency Injection it is, in my opinion, much easier to create a code structure which allows us to do Continuous Delivery.
The impact and importance of Dependency Injection can only be fully understood and appreciated by spending time with it on real-world projects. Personally, I had a hunch before I started to use DI that it could be useful. Today, I find it hard to imagine how to get by without it.
If you can accept that DI is almost a corollary of SR, then you can probably also accept that the way to achieve the O of the Solid Principles – Open/Close (OC) is due to DI and SR. OC means that the code must be open for extension but closed for modification.
When I say that baby steps must be additive, i.e. that new code is added but existing code is left unmodified, then I really refer to OC. When developers respond that OC is hardly ever possible, I acknowledge that it is an ideal that cannot always be achieved but if you pay close attention to the responsibilities of your code and inject all dependencies it is surprisingly often possible to follow OC. I don’t blame developers who get OC explained and then fail to understand how to put it into practice. My advice is to focus on SR and DI for some time, then revisit OC – it will most likely make much more sense then.
The I of the SOLID Principles, Interface Segregation (IS) – means that you should generally depend on client specific interfaces, not concrete implementations. That is almost a no-brainer, as you could not inject dependencies, leaving the IoC container the responsibility to create whatever concrete instance that makes sense, if your code depended on concrete instances. And having bloated, non-client specific interfaces would be a violation of the SR principle.
Depending on interfaces rather than concrete implementations means that you will have classes with interfaces, even though each of those classes only have a single implementation. Some developers dislike that. On the other hand, IS does not demand that all classes have an interface. For example, instances that are essentially values to be passed around can be newed-up independently of the IoC container and do generally not need an interface.
The final letter of the SOLID Principles is L – Liskov Substitution (LS). This principle means that you need to restrict yourself when using inheritance. To some developers, this is highly provocative since inheritance is core to object oriented programming and restricting its use seemingly means crippling the underlying principles of object oriented programming.
Personally, I learned early in my career how easy it is to get into trouble if you happily use inheritance without giving it a second thought. Deep inheritance hierarchies is probably a thing from the past that nobody would create today, but even with a single level of inheritance, even without multiple implementation inheritance, it is so easy to create unmanageable code. I actually abandoned inheritance completely for a few years since I could not see anything but traps and pitfalls. I am happy that Liskov was handling it more intelligently.
LS essentially means that if you inherit a class S from a class T used in your program, then you can use S in your program and it will still work according to requirements. In other words, if your program is working with your composition root set-up with T, it must also work with the composition root set-up with S. Note that the principle goes beyond just plugging-in S to see if it will crash and burn your current system – LS states that you must be cautious whenever you inherit as otherwise you will see problems in the future. It is about maintainability.
Where are we now?
So where does all this leave us? If we follow the SOLID Principles we will automagically have Continuous Delivery?
I am afraid not. Code structure is only one among several cogwheels in the larger machinery of Continuous Delivery.
However, I firmly believe that these principles are essentially common sense and that putting common sense in code will never harm.
In fact, it’s hard for me to imagine how we could have had Continuous Delivery work so well for us in TradingFloor.com if we had not focused on at least Single Responsibility and Dependency Inversion.