Let us start with The single responsibility principle:
Gather together the things that change for the same reasons. Separate things that change for different reasons.
some see it as:
do one thing and do it well
The problem with this principle is that the definition is open for interpretation. The principle assumes sufficient experience around what kind of changes in requirements we can expect (something that depends on the people involved). I’ve worked with people that have a good grasp on how large a class/module or function should be, but the problem is that people have different expectations around what kind responsibility/concern code should have. It is a good thing to keep code focused on one thing, so the principle should be more of a reminder to break out or move code that does not fit. As Marco Cecconi in ‘I don’t love the single responsibility principle’ mentions, to avoid making a mess you need balance coupling and cohesion (not something you would expect a developer that has only worked for a few years to know). It can still be good to have it in mind.
A Module should be open for extension but closed for modification.
Mentioned in The Open-Closed Principle. This is another principle that you need to have experience in order to apply in a good way. The risk is that it becomes as Dan North says
Cruft Accretion Principle
I don’t agree with Dan North. If you have smaller code blocks as mentioned in single responsibility principle you can garbage collect old code.
Perhaps some of the issues around this principle is that it becomes overly ambitious scope if taken out of context. If we pull back on the ambition and instead focus on some of the things he talks about in the article we can summarize as following:
The reason for the principle is that you want to avoid cascade of changes due to a single change of one code unit. That is
Allowing you to change behaviors without modifying code
An example is given how it is brittle to write code that uses “runtime type identification”. Why is that brittle? The reason it is brittle is because if you have a class that is not
sealed in C# or
final in Java or C++, someone can easily inherit and break old assumptions.
My take on the case of OCP is that you either make code:
My feeling is that this principle makes most sense when talking about
The Liskov substitution principle is not formulated mostly by Uncle Bob, why it has a slightly different feel to it.
An implementation of an abstraction should be replaceable with other implementations of the same abstraction
or formulated in a different way
Code unit that publishes conformance to contract should conform to the contract
or in this way
A program that uses an interface must not be confused by an implementation of that interface.
We note that IReadOnlyCollection is more about what Dan North says:
That is, if we send in a list to a method that accepts a read-only collection then we assume that the method wont mutate the list and the method assumes that we won’t mutate that list (during the execution of the method). This means that we are back at a more fuzzy interpretation rather than a strict principle. My feeling is that even though it is benign to let lists act like readonly, you are opening yourself up to trouble in the long run since it is a detail that is easy to miss.
Clients should not be forced to depend upon interfaces that they do not use.
or also formulated as
Keep interfaces small so that users don’t end up depending on things they don’t need.
The intention of this principle is that you should not require your code to depends on “fat” interfaces (or collection of unrelated interfaces).
The problem with this principle is that there isn’t a sufficiently clear definition of when to apply this principle. In many cases depending on C#
ICollection<> is better instead of a specialized interface with only the methods that you use. You rarely implement your own collections, rather you usually depend on existing framework implemented collections. If you instead try to limit this principle to business code (instead of infrastructure) you can end up with infrastructure code that makes your code hard to test when there isn’t any provided testing tools for the infrastructure.
modules that encapsulate high level policy should not depend upon modules that implement details. Rather, both kinds of modules should depend upon abstractions.
or also formulated as
Depend in the direction of abstraction. High level modules should not depend upon low level details.
Why? My feeling is that he describes a situation with a solution with a mix of business logic and low level implementation logic. This principle makes most sense when you want to use an architecture such as adapters/hexagonal/clean architecture. Understanding that type of architecture requires you to be familiar with its culture and purpose.
If we are talking about two different things such as high level modules or detail being business code and low level modules being databases, integrations and hardware implementation. If we code directly against a specific database/integration/x86 architecture then we know that the code is less portable. Having infrastructure interfaces that includes business details means that the infrastructure is tied to that business domain. That is generally fine for solutions that only caters to one business domain but not fine for general purpose code such as say Entity Framework Core or Hibernate.
One of the big question I usually have around clean architecture is if you want to isolate yourself from the external infrastructure (such as Hibernate or Entity Framework Core) or if the usage of such frameworks can be seen as in line with the goal of having a way to isolate yourself from too much details about the database. The previous iteration of Entity Framework, called only Entity Framework instead with the “Core” tacked on, had a higher coupling with the database.
We should not design for reuse in our business domain, but rather if we are building software that is intended for many business domains. This means that by misunderstanding this principle it can lead you into writing “reusable” code that should never be reused.
My feeling is that SOLID makes sense. It does however require you to have worked as a software developer/engineer enough in order to get the experience needed to understand them. Working on complicated code and gaining enough experience will net you a feeling of what is common sense aligned with SOLID. Working in other projects that do not have sufficient complexity and enough business logic may well give you another view. I’ve talked with coworkers that have seen the failure mode of teams trying to apply the principles in a mechanical way. SOLID experiences what what Eric Normand denotes The Christopher Alexander Effect.
Do you want to send a comment or give me a hint about any issues with a blog post: Open up an issue on GitHub.
Do you want to fix an error or add a comment published on the blog? You can do a fork of this post and do a pull request on github.