This might seem like a rather academic question - to compare and contrast the importance of the SOLID principles. Does it really matter? Surely, they are all important?
They are, but one of the challenges with the principles is that they can themselves feel somewhat academic. Wrapped in opaque, off-putting names like the 'Interface Segregation Principle', developers (and especially newbies) can struggle to relate the principles to their world - why would you care about making a module 'open and closed' if you are only developing a simple web application?
Even the experts can't agree about some of the finer details:
Few people realize that the single responsibility principle and the open/closed principle are the same thing.
— Michael Feathers (@mfeathers) July 23, 2013
@glv @mfeathers If you attend my SOLID Deconstruction talk, I make slightly different connections, including OCP as special case of LSP.
— Kevlin Henney (@KevlinHenney) July 23, 2013
Plenty of material exists to introduce and explain the principles – their name, a detailed explanation of what the name means and examples of how to apply – but should you be using and thinking about them all the time, or, only occasionally when the situation requires them?
The answer is yes, no, and it depends.
A Quick Recap
First, a quick recap on the principles. I am not going to go into that much detail as there are umpteen articles on this subject. Follow your tail on this. This post is more about the everyday relevancy of these principles.
- S - Single Responsibility Principle: Every module, class, function should have only one reason to change
- O - Open-Closed Principle: Modules should be open for extension but closed to modification
- L - Liskov Substitution Principle: Subtypes should never break the supertype's contract
- I - Interface Segregation Principle: Never force a client to implement methods it doesn't use
- D - Dependency Inversion Principles: Decouple the layers of your software through abstractions
Extensible Software
Together, the OCP, the LSP and the ISP are all about extensibility. They provide guidance on how to create and use extensible code. If you are required to develop software that must be extendable by design, then these principles come into their own - they are related and supportive of one another.
For example, it should be possible to extend the behaviour of a third-party library without having to edit and change the actual library code. Users should be able to inject variability through abstractions, strategies, lambdas, etc. This is the OCP.
These 'variability' hinge points, often enabled through interfaces, should be narrow (with a single method) so that implementators need only implement that method because only that method is required to affect the variability. This is the ISP.
And whatever you do, make sure your implementations spring no surprises. Do not break the terms and conditions of the interface being implemented e.g. by throwing an unexpected exception, otherwise you will break the library. This is the LSP.
Basically, if you need to create clean, extensible code, then you will need to apply these three principles. But remember, extensibility is not a required property of all software. As Lukas Eder (creator of Jooq) points out in his excellent overview of the OCP:
the OCP "is a very desirable aspect of some software entities."
The key word here is some.
Layering and Decoupling
The Dependency Inversion Principle provides advice on how to layer and decouple your software - high-level layers depend on abstractions not on the implementation details of the lower-level.
This means that implementation details should never leak through your abstractions. E.g. The mechanics of your database logic should be encapsulated entirely within your data access layer, and never exposed to the business layer of your software.
The Dependency Inversion Principle forces you to think more deeply about the abstractions, interactions and T&Cs between your layers. This is a good thing.
The DIP is a general principle that can be applied in all software, no matter the context. It is relevant between process boundaries and within processes e.g. across the view and model layers. Any time you need to break a bunch of connected responsibilities into a new layer (package), then think Dependency Inversion. At the same time, never lose sight of the fact that code that changes together belongs together. More damage has been done across vast swathes of code in the name of simplicity by creating pointless layers of abstraction and unnecessary packaging than I care to count.
Do One Thing
So, that leaves the Single Responsibility Principle.
The SRP is essentially about doing one thing at a time, be it writing, reading or reasoning about your code. As a way of thinking and working, it affects your decision making at all levels of your software.
In the big, the SRP encourages you to organise related code into components, modules and packages; and to partition and separate out concerns that don't. These are the flip sides of the same cohesive coin. In the small, the SRP pushes you to create short classes and methods that each do one thing (or more precisely, have one reason to change).
The SRP was initially written as 'A class should have only one reason to change'. I take a broader view - for me, it tells me to think about cohesion across all coding structures, not just classes - packages, methods, modules, components, etc. For that reason, the SRP is applicable everywhere in software. It influences everything from how you design entire systems right down to how you write individual methods.
As Bob Martin says, the SRP is one of the most foundational principles of good design. It is the principle from which many other principles flow. E.g. Whether or not you agree with Michael Feather's assertion about the SRP and OCP being the same thing, the OCP is at the very least an example of the SRP in action.
One Principle To Rule Them?
In practice, you will find yourself using about the SRP all the time, at all levels of abstraction. It is such an important guiding principle that it deserves to stand out on its own.
It provides guidance on how we design our microservices and serverless functions, it affects how we package and organise our class hierarchies and namespaces, and it informs us when to decompose behaviour out into new classes or methods.
Is it the one principle to rule them all? Maybe not quite, but the key point here is that the SRP is such a foundational principle of writing software that it shoulld be completely locked into how you think about and write code. This is not to mitigate or lessen the importance of the other SOLID principles, or indeed other principles such as DRY, CQS or RAP, but in many ways the SRP is the bedrock of how we build software.