1

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

10
  • 3
  • 1
    @ThomasJunk If I ask 10 different people they will give 10 different responses for what SRP is. The only thing that we can use objectively is the definition given by Bob. And as per his definition, and my reflection of it in the question, the websocket client violates SRP.
    – Ash
    Jun 14, 2023 at 8:06
  • 1
    Don't nail me on this one, but I believe "one layer of abstraction" is a definition Robert (Uncle Bob) C Martin gave himself in a talk some years ago. But I can not cite it correctly. OTOH argumentum ab auctoritatem does not really help. Jun 14, 2023 at 12:36
  • 1
    @Ash Responsibilities are defined at the business/requirements level, not the implementation level. Sockets are nearly always an implementation detail (that is to say, your business requirements usually wouldn't specify networking detail because the people paying for a system usually don't care). A generic socket class generally doesn't have any "responsibilities" because it's just some generic reusable bit of library code which doesn't satisfy anybody's requirements by itself, it needs a load of other code around it before it can do anything meaningful for your stakeholders. Jun 14, 2023 at 17:15
  • 1
    To that end, the only way a websocket class could have a responsibility for anything would be if you have a stakeholder (maybe a highly technical customer who needs to integrate with your system, or someone senior in the organisation who is trying to standardise on a set of technical/architectural decisions), then the responsibility would be that person's requirement for a websocket (the issue with multiple responsibilities would only creep in if you had some other stakeholder who also needed some alternative needs around networking where the websocket library doesn't fit their needs) Jun 14, 2023 at 17:26

4 Answers 4

2

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.

12
  • Greg: Thanks. Upvoted. Appreciate you quoting from the referenced article to keep the discussion objective.
    – Ash
    Jun 17, 2023 at 4:25
  • Greg: much of what you say is my understanding of things as well. By separating concerns, you increase cohesion while reducing coupling. If I were to design the Websocket class, it would end up pretty much exactly with the same functionality as the .Net link I provided. But I'd get there simply by separating concerns alone (up to a point that is intuitive and logical). So if SoC already gets you the cohesion/decoupling that you ought to strive for when designing classes (or modules, or bits of your architecture, whatever), it goes back to my question: what else does SRP provide?
    – Ash
    Jun 17, 2023 at 4:31
  • @Ash: the intent is the SRP is not separation of concerns. It is managing change. You might arrive at the same design using both principals, but that doesn't mean one is a rephrasing of the other. Many principals and design techniques lead you towards similar designs, but they are concerned with different aspects of that design. Jun 17, 2023 at 20:17
  • Greg: we are getting closer. Fyi, I’m not saying SRP is rephrasing SoC, I’m asking if it ultimately helps solve something SoC doesn’t already. ‘Managing change’ is all well and good but it’s just a phrase. You have to question how? I.e., what is it really doing with ‘managing change’ other than increase-cohesion/reduce-coupling? Because SoC already does that. One way to prove would be a counter-example that shows two classes where SRP has been applied and then showing how SoC alone could not have got you there.
    – Ash
    Jun 18, 2023 at 11:14
  • 1
    @Ash: I think I'm starting to see where your question lies. Remember that all of these buzzwords tackle design from different angles, while being complimentary and competing, depending on the problem. Jun 18, 2023 at 12:33
12

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.

I'm not surprised you find this pragmatically impossible, by literal definition of what pragmatism means.

Pragmatists don't do things "always", that's dogmatists. Pragmatists work on a case-by-case basis.

For example, writing a websocket client. [..] It lets you connect AND disconnect AND read AND write AND other stuff.

What is a websocket? It's a line of full-duplex communication, which entails that both sides of the connection are able to send and receive messages. Wikipedia link. This justifies why you have both a write and a read method.

Why do we connect to a socket? So that we can open up this line of communication.

Why do we disconnect a socket? Because we previously connected to it and no longer wish for it to be connected.

All of this describes a single responsibility, which we call the websocket. Everything you mention works towards a single goal: to allow full-duplex communication, which is why we created websockets in the first place.

Is there anything beyond SoC that SRP provides?

It is folly to consider these terms as scientifically precise. There is no singular objective measurement of what SRP and SOC is. It is a guideline intended to point at a bad situation and how to avoid it. It is not a provably unique solution, not a solution that is provably distinct from all other solutions.

Overlap is very common. Not just with SRP, but with all of SOLID (and similar paradigms). It's hard to point at one letter and provide an example to which the other letters don't also apply, but that is not the purpose of these principles to begin with.

I feel like you're succumbing to dogma and an underlying assumption that these labels are scientifically and objectively precise. They're not. Don't try to apply them as definitive criteria. Instead, understand what the spirit of the advice is, and apply that spirit when you are developing. Don't try to chase the dragon itself, it's not productive.

25
  • Thanks. All of the arguments you provide for having the websocket client contain those different methods are precisely what I mentioned about that being its area of concern. Can you elaborate how it doesn't violate SRP however, if Bob claims that it must have one and only one reason to change? What if the components of the program that use it for writing need something changed about how it writes - this affects those that only use it for reading, does it not?
    – Ash
    Jun 14, 2023 at 6:22
  • 3
    @Ash: I really wish we could throw out the "reason for change" argument because it is so frustratingly vague and liable to be interpreted in wildly different ways. Responsibilities and how you define them is so contextual that you can't meaningfully describe exactly what they should and shouldn't be. The point is to create a cohesive unit with one goal. Also, I think you're conflating changing the websocket itself with changing how the consumer of the websocket chooses to interact with it.
    – Flater
    Jun 14, 2023 at 6:31
  • I'm glad you mention 'vague'. The vagueness of SRP is what stems all this, including the conflation you mention. In his definition, Bob doesn't make it clear whether a change is internal or in how the consumer interacts.
    – Ash
    Jun 14, 2023 at 8:14
  • 1
    @Ash: I went to the butcher and asked for 500 grams of mince. He put some on the scales and it measured 488g, so he added some more. It ended up as 501g. The butcher sighed and broke out the milligram scale. After a few minutes of finagling, he got it down to 500,003mg. He sighed again, and realized that he needed a smaller spoon to work with precise increments, and left to go and buy such a spoon. I really wish he'd just given me the 501g of mince because I just wanted to make some burgers for dinner.
    – Flater
    Jun 16, 2023 at 1:14
  • 1
    @Ash "I’ve been developing for over a decade" You could have fooled me. Anyway, I think it's time to answer your question. The answer is "No". You figured out the big secret in software engineering. The elephant in the room that no one talks about. It's about time someone pointed it out. Bravo. But we're not done here. It raises the question: isn't SoC just "common sense"? I mean, what does it really provide over "don't be a fool"? I think you're on to something here. Jun 19, 2023 at 6:26
5

It is a matter of perspective.

SoC is a general design principle that allows parallel development by minimizing dependencies. It is applicable to multiple methodologies.

SRP is part of a set of rules directed at object oriented design.

Where SoC says "this is how you approach anything", SRP provides a rule to test whether a class adheres to SoC in an OO context.

They do boil down to the same thing. One is looking at things from outside and the other is looking at things from within a class. The latter makes sense when you are building a class tree in an OO setting.

As OO gets old and people are no longer willing to pay money for an OO course or a book on it, you will likely see offerings of shiny new methodologies that come with their own set of acronyms. And chances are SoC will be in there somewhere as well by yet another name.

[Edit]

OK, since the OP keeps coming back to the question "What is it good for?", here are some pointers.

If it (a class) has multiple responsibilities, then

  • You may find yourself breaking multiple things when working on one thing because it would be hard to avoid impact on feature B when changing feature A in the same class.
  • Your code would be less flexible, swapping out one implementation of a feature for another would be impossible as long as that feature is tied to other features you can not afford to lose.
  • It would be hard to find where you need to be to apply a fix or change in behavior because the class name would inevitably be inapt.
  • You and your co-workers would be more likely to get in each other's way, leading to disgruntlement and merge conflicts.
  • It would make the product harder to understand, to build a map in your mind of how the parts work together. This makes it harder to find people capable and willing to work on it and thus more expensive and more time consuming to maintain.
11
  • Is there anything beyond SoC that SRP aims to provide though?
    – Ash
    Jun 14, 2023 at 6:59
  • @Ash SRP it is just more specific to the task. Like "Be careful with sharp objects" vs "Do not run with scissors". Jun 14, 2023 at 9:33
  • Sure. So specific rules to ensure SoC conformity aside, does it provide any other benefits? I.e., if the end goal is SoC, does it need to come via SRP? Because I can say a program has sufficient SoC but it may not be as a result of every class adhering to SRP - in fact, that's pragmatically impossible as I've detailed in my question.
    – Ash
    Jun 14, 2023 at 9:55
  • 1
    @Ash: Just in your last comment alone, you mistakenly reframe a lack of answer to your question as a lack of knowledge which presumes that your question is meaningfully and definitively answerable ("clearly a vast majority of developers don’t even know ..."), subjectivity ("all varying levels of SoC to me"), a blind assumption that Martin's list is somehow complete (which you cannot reasonably assume) and have significantly moved the goalposts by now talking about "a relationship to SoC" which is decidedly more vague than the rigorous nature of your core question.
    – Flater
    Jun 18, 2023 at 23:08
  • 1
    @Ash: You are trying to hold these guidelines to the standard of a scientific law, and your last comment implies that anything that does not meet that standard is "ridiculous" if developers abide by it. Your belief that developers should not abide by anything less than a rigorous definition is exactly what I'm trying to point out. This is your opinion, and that's okay, but you're jumping to concluding that this is therefore the only right way to go about development in general, and it clearly isn't (given SOLID is considered to be an essential guideline to developers everywhere).
    – Flater
    Jun 18, 2023 at 23:12
4

When Martin talks about "change originating from a single person" he is not talking about runtime state changes initiated from other software components. He means requirement changes coming from different persons or departments in an organization. The kind of change when you have to modify some parts of the code.

The financial departments will have some requirements while the marketing department will have different requirements. These requirements tend to change independently, and therefore a single class should not implement requirements from multiple departments.

It means letting the separation of concerns which exist inside an organization be reflected in the software architecture.

It wouldn't apply to a low-level component like a socket library. It is not like the HR department requested the Connect method and the CFO requested the Disconnect method.

0

Not the answer you're looking for? Browse other questions tagged or ask your own question.