Recently, I heard a statement: “We hired a consultancy to make a code review for our team and they said our code has a lot of technical debt, because it is not SOLID.”
The SOLID principles (Single Purpose, Open-Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) are five basic principles of Software Engineering. However, there is a reason why they are “principles” and not “laws”.
The small picture: Never “Done”
I will assume for a minute that our team has a DoD stating “Code must be SOLID”. Now, we are in a mess. Suppose I write a mere “Hello World” program. My first draft will have a static main printing “Hello World” to the screen. But that's not Single Purpose: Text and screen output are in one method. Let's separate them. Oh wait – I have another problem now, my print method depends on an implementation of my text method – fixed. Let's add an abstraction layer. Oh wait – we have a problem now. I need to extract the interface. … I think you get the idea. Give me any piece of source code and I can both tell you which principles it violates and also which principles will be violated when you “fix” the violations. But: Where is the value in that? Is our goal to make perfect code or to deliver Customer value? You should be “Done” when the product meets its purpose, not when you think it looks perfect.
The medium picture: SOLID != Clean Code
Let us start by clearing out a basic misunderstanding first. Clean Code is code that is easily comprehensible, easily maintainable and easily adjustable. SOLID is a set of principles to keep in mind to reach this objective. But SOLID is not the only thing you must do.
To make code easily comprehensible, you may need code conventions and syntax checkers – and you will definitely need a well written, readable tests.
To make a product easily maintainable, you need Continuous Integration, not just as a tool, but as a mindset. You should be working on a usually Green Master that is sufficiently robust to give developers rapid, useful feedback. This, of course, requires lots of thought put into your test suite – potentially more than you'd put into your productive code.
To make a product easily adjustable, you need to think not only about your source code, but also about architecture and design. This, in turn, means that you must refactor not only on code level, but also on infrastructure level.
The big picture: Agility
Inexperienced agile teams may be tempted to add “Code is SOLID” to their Definition of Done. While it is a good idea to produce clean code, SOLID is not a desirable purpose in itself. First and foremost, why do we strive for Clean Code?
Behind Clean Code is the 4th agile value “Responding to Change over following a plan”. At the same time, you have the 1st Agile Principle: “Our highest priority is to satisfy the customer through early and continuous delivery of valuable software” and 10th Agile principle: “Simplicity – the art of minimizing the amount of work done – is essential!” Clean Code has the objective of making code easily workable, with the goal of creating a flexible product that can be adjusted incrementally without ever slowing down in pace. To reach this objective, we must be able to minimize the effects of change. This requires changes to be elementary and good automated test coverage. And this is where SOLID comes into play: When you need to invest a lot of effort, either into making the change itself or validating that change, your code is not good. SOLID gives you a simple set of rules, not only how to add a change, but how to structure your code so that you can change it later.
Agility neither advocates keeping SOLID principles nor disregarding them. Much rather, SOLID is a tool to minimize overall work done – both now, and in the future. When you know with a high amount of certainty that the amount of work you will invest on a piece of code in the future is lower than the amount of work you would invest into SOLIDifying your code now – then don't. That's not in the spirit of agile software development, because SOLID is not a customer value: Working software is.
Whenever you make a change to the code, you should make this change in a fashion that will not purposefully reduce the quality of your code, and that means: Future comprehensibility, maintainability and adaptability should not deteriorate below their current stage. When changing code, think about refactoring before you leave.
In refactoring, remember: Minimize the total amount of work done. Do not apply “SOLID” when it's simply a waste of effort. On the other hand, apply SOLID when you know for certain that you will get back to a code segment.