This is part 1 of a 5 part series, going through each of the principles in SOLID, how they have aged, and investigating if we can achieve a deeper more comprehensive understanding of what each principle is trying to achieve.
SRP - The Single Responsibility Principle
A common misunderstanding
To Uncle Bob's own words, SRP is commonly misunderstood to mean
A module should do one thing and one thing only.
In reality, Uncle Bob meant SRP to be a corollary to Conway's law, which in simple terms state that software will naturally be segregated to mirror the topology of the teams building the software.
Microservices is probably the most extreme example of this law. If you have one team dealing with user notifications and another dealing with user analytics you can bet that these two features will end up being split into separate services. This is because the communication overhead required to merge it into a single service is high, so there will be a natural resistance to it.
If SRP is indeed supposed to be a corollary to Conway's law, SRP could be stated as
A module should only belong to a single team.
Now, Uncle Bob doesn't state it this way - rather he states it in terms of whom the user is. That is,
A module should be responsible to one, and only one, user.
A "user" here isn't meant to be a single person. Rather, it's a type of user. For example, just because there are billions of people using Facebook doesn't mean that all those users have their own code modules. Rather, one user type might be a user creating events for their business, another might be people sharing pics with their friends. Their use-cases and feature set are completely different.
Now, an important thing to note is that the target user type of a piece of code is not necessarily the same as the team maintaining it! That is:
SRP as Uncle Bob states it only applies if your team topology is a one-to-one match with your user types.
I personally believe that product-aligned teams are a good idea, which naturally implies user-type-aligned teams. But not all organisations are structures this way (for example a company might have DB team, and an AI team, but they each support many different products), so in those cases this distinction is important to note.
I couldn't find clarity in which Uncle Bob belives is more important; the segregation on team or segregation on user group.
How does SRP help?
Now that we've cleared that up, let's go into the issues SRP is supposed to solve.
Uncle Bob brings up the inherent issues in code shared by multiple user groups. In Clean Architecture, he gives two examples:
1. Relying on shared functionality
The example has two teams relying on a single function to calculate the payroll of employees.
One day one of the teams decide to change the logic of that function. This causes unexpected and erronious behaviour in the other teams product, because it did not expect such a change.
2. Merge conflicts
Two teams decide to mutate a single function in different ways, causing merge conflicts where, if a bug were to pop up, now risks impacting 2 user types instead of just one.
Now, the main thing I want to highlight here is that SRP is supposed to alleviate issues that arise in real software - that's why it exists in the first place. So let's focus on these issues. Are there any other lens in which we can look at these issues?
I argue that both issues above are inherent issues in shared code, i.e. code that is depended upon in more than one circumstance. They have nothing to do with team topology, user group topology, or even the software topology.
If you as a team decide to use a shared piece of code, you should be prepared for the consequence of the fact that the semantics of that code is also shared, i.e. all users of that code will all do the same thing.
(By "shared code" I mean any code that you do not have exclusive access to mutate, and that has multiple usage sites. It could be an in-house repo of common functions that all teams share, it can be logic behind a third party REST API, an npm library that's automatically updated to the latest version, a DLL that's dynamically loaded on the target machine, etc.)
In example 1, if multiple teams are using a single function to calculate payroll, the purpose for sharing that function should be that they all calculate the function in the exact same way for the forseeable future (until a team decides to deviate.) If a team doesn't want to do the exact same payroll as another team for the forseeable future, they should not be sharing code - each team should be doing their own payroll calculations!
In example 2, if you have a merge conflict in a shared function, it is because you inherently want all involved parties to change their behavior in that particular way, otherwise you would have your own version of it. This needs to be resolved if you want the behavior to be shared. It's a real-life business logic conflict that needs to be solved, not just an annoying code organizaton issue.
The underlying lesson here should rather be that unless you explicitly want to use shared code for its inherent property of being shared, shared code is a liability, not a virtue.
Revised version
With that in mind, I'd like to reshape SRP into this:
Use shared modules if you
a) Are also willing to take on the liability of it being shared
OR
b) Want to explicitly ensure that the behaviour of your program is shared with other users of that module.
Otherwise, you should copy/freeze the logic you are relying on.
(Side note: Before all the DRY people come at me, "copying" doesn't necessarily mean literally copying code. It could be freezing a version of a dependency, using a library instead of a service, etc. It's just any code that is not automatically updated by someone else than you)
SRP is unfortunately now a really poor name for this principle. Maybe we can squint a bit and pretent the abbreviation stands for ShaRe Purposefully?
Also, note that this no longer tells you how to split up your code. That's a big loss! Have no fear, we will have better guidelines for that when we look at the other principles in later posts!
Next time we'll looking at the O in SOLID - the Open-Closed Principle. Stay tuned!
Top comments (0)