Design Patterns – Intent Behind SRP Beyond Separation of Concerns

design-patternsseparation-of-concernssingle-responsibilitysolid

Edit based on responses so far
I am starting with an edit so as to save future contributors from going down the same path others have.

I am only interested in contributions that stick to the exact definition of SRP I have quoted below. So no “you can think of SRP as…” type elaborations, as that takes away from the objectivity.

First, a bit of background to give more context to the question.

To keep the discussion focused, I will only be talking about classes here, not functions, or modules, or any other construct. So where you read any of these other constructs, think 'class' instead.

The definition
Let's start with how Robert C. Martin (Bob) puts it:

The Single Responsibility Principle (SRP) states that each software module should have one and only one reason to change.

He goes on to say:

When you write a software module, you want to make sure that when
changes are requested, those changes can only originate from a single
person…
Why? Because we don’t want to get the COO fired because we made a
change requested by the CTO.

As a side note, if anyone is to be fired here, it's the person who decided that unit tests were not required – ensuring nothing is broken following a change is exactly the 'concern' of unit-tests run on the pipeline.

Back to SRP…

I find it pragmatically impossible to always adhere to it, and unless I am mistaken, it certainly appears to be what Bob is intending we do; apply it everywhere. Sure there are some classes where you can easily implement this pattern. In fact the typical marketing done in favour of SRP would use examples like "Report generator should not also format a report" and so on…
Sure. Agree. Easy. Why wouldn't you separate out those two pieces of functionality!?

But think of other very-common situations you are likely to encounter during development. For example, writing a websocket client. Here's a sample implementation: https://learn.microsoft.com/en-us/dotnet/api/system.net.websockets.clientwebsocket?view=net-7.0
It lets you connect AND disconnect AND read AND write AND other stuff. Clearly it (along with a thousand other classes across .Net and other runtimes/frameworks) violates SRP.
Bob describes change in terms of "actors", which when talking about a class, boils down to consumers of that class.

With a websocket client, there might be some parts of your program that only need to read from it, while others need it for writing. A change request from one part can have a bearing on another part, so based on the definition of SRP (I urge you to pause and read the Edit at the top), the websocket client violates SRP.
Now, you may be inclined to argue that the websocket client can be thought of as self-contained, bounded, a singular entity, or some such. However these are all concepts relating to SoC, not SRP (as per its definition), and I would urge you to refer back to my edit at the top once again.

Finally, the question:
Is there anything beyond SoC (Separation of Concern) that SRP provides? If so, it would help to provide an example, that clearly describes a problem not solvable using SoC alone. Preferably something with minimal code and realistic, so no God class or such that you really ought not to be doing anyway as a developer.

Ref: https://blog.cleancoder.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html

Best Answer

The concepts of Separation of Concerns, Cohesion, and the Single Responsibility Principal are all complimentary. The key is to understand that change, and more importantly enabling change without breaking other things, is the driving force behind the SRP. Ensuring your classes adhere to Separation of Concerns and are Cohesive all support this goal.

Separation of Concerns focuses on separating software into distinct sections/modules/components. The goal is modularity and code reuse.

Cohesion is an abstract measure of how well the data and methods of a class support the class's intended purpose in the system.

Robert Martin, towards the end of his blog post, elaborates on this relationship between the Single Responsibility Principal, Separation of Concerns and Cohesion:

Imagine you took your car to a mechanic in order to fix a broken electric window. He calls you the next day saying it’s all fixed. When you pick up your car, you find the window works fine; but the car won’t start. It’s not likely you will return to that mechanic because he’s clearly an idiot.

That’s how customers and managers feel when we break things they care about that they did not ask us to change.

This is the reason we do not put SQL in JSPs. This is the reason we do not generate HTML in the modules that compute results. This is the reason that business rules should not know the database schema. This is the reason we separate concerns.

Another wording for the Single Responsibility Principle is:

Gather together the things that change for the same reasons. Separate those things that change for different reasons.

If you think about this you’ll realize that this is just another way to define cohesion and coupling. We want to increase the cohesion between things that change for the same reasons, and we want to decrease the coupling between those things that change for different reasons.

The first paragraph in the quote above illustrates what I believe are his main motivations for the SRP — to change or fix something without breaking something else. He specifically mentions "separate concerns" and "cohesion" in this series of paragraphs, because you cannot create a system you can change safely without separating concerns and making sure your classes are cohesive.

Martin rephrases his own invention in a different way to emphasize the connection to change:

Gather together the things that change for the same reasons. Separate those things that change for different reasons.

The goal of SRP is to design a system that can be changed without things devolving to bug whack-a-mole. In order to do that, you need to separate your concerns to create a modular application, and create cohesive classes, which provides a balance between separating things and keeping things together.

I've always had a hunch that the Single Responsibility Principal is the mortar that holds the "Separation of Concerns" brick together with the "Cohesion" brick. These three concepts are intertwined, but accomplish different goals in application design.

To see where these two concepts can result in different designs, consider situations where enabling change is desirable, but code reuse is not.

Let's say you have a data access class connected to a RDBMS. This involves two main concerns: interacting with the database and mapping query results to objects. By just considering separation of concerns, you could justify two different classes: a mapper and a SQL gateway. Now you want to swap out a SQL database for a web service. Since the web service returns different data structures than a SQL database, changing from a SQL gateway to a web service gateway requires you to change the mapper, too.

But is this separation useful? A change in one component requires a change in another component. Do you ever need to reuse the SQL mapping in other classes? The answers to these questions depend on the system design. If you need to reuse data mappings, then placing that logic in it's own class makes sense. If you only have a single class using this data mapping, and every time you change the gateway you need to change the mapper, then you gain nothing by separating those two concerns. This is where the single responsibility principal offers additional advice that can simplify the design:

Gather together the things that change for the same reasons. Separate those things that change for different reasons.

Yes, it's the same quote mentioned above. Because it's the really important part.

If the mapper changes every time you change the gateway in your data access class, the SRP says, "then don't separate it!" The idea is if you don't separate these things, then you are less likely to introduce a bug when changing one of the components.

SoC says to separate things to make them reusable. SRP says to separate things when they change for different reasons, and don't separate things when they change for the same reason so you don't accidentally introduce a bunch of bugs.