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

Joris Timmermans
  • 8,998
  • 2
  • 36
  • 60
Ash
  • 219
  • 2
  • 6
  • 3
    https://softwareengineering.stackexchange.com/a/345020/38663 – radarbob Jun 14 '23 at 05:00
  • @radarbob Thanks. Does not directly address my question. I'm not asking for when to use SRP or what constitutes a responsibility, I'm asking if there is anything beyond SoC that it achieves and (as per question) example to demonstrate if it does. – Ash Jun 14 '23 at 06:17
  • A better way of understanding `Single Responsibility` is by thinking it as _the same layer of abstraction_. That said the web socket example makes perfect sense: connecting, disconnecting, reading and writing happen all on the same level of abstraction. – Thomas Junk Jun 14 '23 at 07:16
  • 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 '23 at 08: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. – Thomas Junk Jun 14 '23 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. – Ben Cottrell Jun 14 '23 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) – Ben Cottrell Jun 14 '23 at 17:26
  • 1
    Uncle Bob tried to narrow down SoC with SRP (SoC is broader). Unfortunately, SRP was -- and still is -- broadly misunderstood as doing one thing. So he clarified in his article that it is about "reason to change" which looks like SoC just expressed from a class maintenance point of view. His attempt to link it to people/decision making does not work in many practices (e.g. small software when a team is responsible for everything, or there is only one owner of the requirements). So yes, SRP boils down to SoC. – Christophe Jun 18 '23 at 12:23
  • @Christophe Thanks. Comment upvoted. Yes, it does appear to be more class-specific, though I'm not sure if it simply (always) boils down to providing you with the same benefits as SoC; hence this question. I feel as if there's bit more to it and Greg's answer below is getting close to identifying a distinction, at least from a practical perspective (formalisation can follow after). – Ash Jun 19 '23 at 02:47
  • Yes, interesting. But soc doesn't end at component level: it's also applcable at class and even method level. A more general way to express it could be "gather together what belongs together. Keep separate what belongs to something else". Now SRP tries to take this from the angle of change, because thinking about change allows to feel about what belongs together. It would have been nice if it would have be of practical interest. But it is still very subjective, and look at SE how many SRP questions are rooted in SRP misunderstandings ;-) – Christophe Jun 19 '23 at 06:03

4 Answers4

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.

Flater
  • 44,596
  • 8
  • 88
  • 122
  • 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 '23 at 06: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 '23 at 06: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 '23 at 08:14
  • Regarding the bits of your answer to the end, I'd argue that there are some principles that are well defined with clear intent. DI for example - helps with unit testability. Interface segregation - helps improve SoC, which is a measure - more SoC means easier refactoring. So my question still remains unanswered unfortunately. Other than SoC, what exactly does SRP help with? – Ash Jun 14 '23 at 08:15
  • @Ash: Interesting rebuttal: DI was not created because of testability. It was created because of _swappability_ of components, and testing is one such scenario where you'd like to swap out a component for a mocked one. You've sort of stumbled into what I'm point out; you described DI in a way that matches how you think about it, but that doesn't actually cover the total use cases that it does. If you were to teach your understanding to the next developer, they're liable to think that DI is a testing pattern, which it isn't (not exclusively). – Flater Jun 14 '23 at 22:48
  • @Ash: Secondly, I could quite easily argue that ISP is effectively "SRP for interfaces", which suggests that SRP completely supplants it. Thirdly, and this is just meant as advice, be careful about statements like "more SoC means easier refactoring". Too little is bad for refactoring ease, but so is too much. Case in point: [Hello World - Enterprise Edition](https://gist.github.com/lolzballs/2152bc0f31ee0286b722). – Flater Jun 14 '23 at 22:52
  • Thx for the correction, yes I agree. I didn't phrase it clearly enough. When I said 'helps with' I didn't mean that's what it was created for, rather what it...helps with. Swapping is obviously the reason for its creation. For SRP, I know it 'helps with' increasing SoC, but my question is precisely that: is that the extent of it or is there more? Also as you have correctly pointed out, too much SoC can be bad, and SRP is at the extreme end where classes not only have separate concerns but each one only has a single concern. – Ash Jun 15 '23 at 01:15
  • ...and a single concern (while good in some cases), are plain horrible in others. A Websocket client whose concern it is to manage a full-duplex connection - Good. Separating it out into a class that's responsible for reading and one responsible for writing - Horrible; can be done, sure, but horrible. But that is exactly what is required to conform to the definition of SRC, which is the only objective info about SRC, short of which is just people's interpretation of it. – Ash Jun 15 '23 at 01:32
  • If the end goal is to simply arrive at some meaningful boundary of responsibilities (read, write, connect, etc, all within the same boundary), then that's an issue of SoC, and we (once again) arrive back at my question: is SoC the extent of what SRP intends to achieve, or is there more? – Ash Jun 15 '23 at 01:33
  • @Ash: I disagree that SRP itself is at the extreme end. It's more a matter of an overly granular definition of what constitutes a responsibility. You can always subdivide any responsibility into subresponsibilities all the way down to the CPU instruction level, but there's a point where further subdivision is pointless, and when you say "SRP is on the extreme end", it seems to me that you've really just defined your responsibility too granularly; and SRP is not to blame for that. – Flater Jun 15 '23 at 02:32
  • Flater: I've defined responsibilities up to the point where the consumers are also classes in my control. This is as per the definition of SRP. Breaking it down to CPU level does not conform to the definition, as the consumers of these instructions are not also defined/constructed by me. – Ash Jun 15 '23 at 03:04
  • @Ash: **SRP does not define the granularity of a responsibility**. That is precisely the point I'm trying to get at. Your judgment of SRP hinges on a granularity that you chose (or that someone advised you to use), not one that SRP itself defined for you. – Flater Jun 15 '23 at 03:11
  • Flater: From Bob: "you want to make sure that when changes are requested, those changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function.". In OO terms, the narrowly defined business function materialises itself as a class (or a method in a class) and where the change goes to is also a class (whether through an abstract interface or otherwise). The change is certainly not coming from or going to CPU instructions. – Ash Jun 15 '23 at 04:00
  • But even if that's not the case and let's say what you say is the case, specifically that SRP leaves it up to you to define granularity, my question still stands: is its end goal simply SoC or something more? <- I've probably asked this question about 10 times across this stack thread. Bearing in mind (as I've noted in my question), Bob's motivation stems from and he talks about SoC throughout the linked article. – Ash Jun 15 '23 at 04:02
  • 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 '23 at 01:14
  • @Ash: What I'm trying to get at with my butcher story is that you're too invested in the minutiae to the point that you're failing to understand the spirit of the advice you've been given. In order for your question to be answered by another person, you and that person first need to agree on the _precise_ atomic definition of all terms being used, and the effort required to standardize that definition is completely irrelevant for real world purposes. The advice that was given was never meant to be observed with the level of precision that you are focusing on, rendering the question futile. – Flater Jun 16 '23 at 01:18
  • Flater: I get all the spirit of advice given and I’ve been developing for over a decade and I know how granular to go such that you have meaningful concerns across your classes. I never asked for advice there. Granularity aside (which I’ve already noted above ^), my question still remains unanswered and no one has addressed it precisely. Again, here it is: aside from better separation of concern, does SRP provide any other benefit? – Ash Jun 16 '23 at 03:35
  • @Ash: Flies _really_ want to get outside through the window, but if they don't take a step back, they're not going to get outside because they keep pushing their face up against the impassable glass. I don't mean for that to be rude, but I can only challenge the frame of your question, I can't make you accept it. The issue isn't with how you phrase your question. It's with what your question is trying to find an answer to. No amount of repetition or rephrasing of your question is going to change that. I don't know how to further convince you to see the woods instead of the trees. – Flater Jun 16 '23 at 03:39
  • @Flater And because of SRP you just met the mince butcher. And you get steaks from the steak butcher, sausages from the sausage butcher and so on. Can’t have one butcher responsible for mince, steaks _and_ sausages. – gnasher729 Jun 17 '23 at 12:39
  • @gnasher729: No you don't. Your example is defeated by what already happens in real life. I will put good money on it that you do not personally get all your produce from the farmer that pulled them out of the ground. You're omitting the obvious step of having a middle man specifically to shield the consumer from such complexities. A supermarket fulfills precisely that role. Alternatively, you can provide bundled access to the sources, which is how market tend to work (where you do maybe talk to the actual farmers/butchers, but they all come together in the market for the customer's ease) – Flater Jun 18 '23 at 22:58
  • @gnasher729: You've struck precisely on the point that I'm sticking by: SRP does not live in a vacuum, and neither does its implementation. You can't reasonably define good (or bad) code as being an example of only [this principle] (or absence thereof), as these principles are just not defined to such an atomic level. Yes, in order to resolve your example it's going to require some kind of composition, Law of Demeter consideration, factory, ... to connect the consumer to the producer but that's not proof of an issue with SRP or its definition. – Flater Jun 18 '23 at 23:02
  • 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. – Martin Maat Jun 19 '23 at 06:26
  • @MartinMaat It's common sense sure, though common sense comes from experience; the merits are not at all obvious to someone without the experience so they are unlikely to apply principles that are 'common sense'. For e.g., it's not common sense for a child to look both ways when crossing the street. The principle here is looking both ways, the merit is not dying. They have to be taught. Likewise, the merits of SoC (not common sense to students or junior-devs) are increased-cohesion/reduced-coupling. So what are the merits of SRP beyond those of SoC - no idea, whole point of this question. – Ash Jun 20 '23 at 12:28
  • @Ash: You pursue a fruitless question. The above comment is decidedly vague an unproductive (relative to what it is written in response to), and the last sentence then just regurgitates the same question for the umpteenth time. We're not going to get anywhere if you ignore the information you're being given and override it with a repetition of the same question. Just because you ask a question does not mean that every question you could ask has actual merit. – Flater Jun 20 '23 at 23:22
  • @Flater Thank you, once again, for dancing around the question like a skilled politician (for the umpteenth time) and not actually contributing anything productive. My opinion on your 'spirit of advice' philosophy hasn't change and it never will. If you find my question fruitless, feel free to downvote it (if you haven't already). – Ash Jun 21 '23 at 01:31
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.
Martin Maat
  • 18,218
  • 3
  • 30
  • 57
  • Is there anything beyond SoC that SRP aims to provide though? – Ash Jun 14 '23 at 06:59
  • @Ash SRP it is just more specific to the task. Like "Be careful with sharp objects" vs "Do not run with scissors". – Martin Maat Jun 14 '23 at 09: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 '23 at 09:55
  • @Ash Respecting the SRP does not mean bringing down every class to an atomic operator. A responsibility can still be a complex task that involves lots of operations. There should be a single denominator though that grasps the class's domain, that bounds what it is supposed to do and makes it clear what it is not supposed to do. That can be a course domain, where the class delegates to helper classes to meet its purpose. The point is that the responsibility should be well defined and can be understood by all stake holders. Just like the responsibilities of people in an organization. – Martin Maat Jun 14 '23 at 11:17
  • Martin, that to me is screaming out SoC. Single denominator, domain, bounds - these are all SoC concepts. SRP has a more rigid (albeit not well defined) definition in terms of when a class is allowed to change. If you are instead looking at it more abstractly, then we can throw the definition out the window. I instead prefer to work rigorously as per definitions. So given its definition, my question still remains - is there anything besides SoC that it can achieve? – Ash Jun 14 '23 at 12:18
  • @Ash I suggest you ignore Robert Martin's definition from now on because it only appears to confuse you. We pointed out that SoC is the more general concept and SRP is an OO-specific reading of it. Practical benefits of respecting the principle were added to my answer. I hope it helps you understand. If not I'm afraid I have nothing more to add. – Martin Maat Jun 14 '23 at 12:42
  • @Ash: _"I instead prefer to work rigorously as per definitions"_ There is an underlying presumption on your end of the definitions being rigorous themselves, which is simply not the case. _Your interpretation_ of the definition and how/when to apply it is not necessarily the same as was intended when these principles were coined. – Flater Jun 16 '23 at 06:06
  • @Flater I am aware. It’s ridiculous that a vague/unrigorous principle made it to being the first letter of the guiding set of principles developers abide by; if it served a unique purpose at least, then I could accept that, but clearly a vast majority of developers (including yourself) don’t even know how to identify any such purposes beyond SoC. For eg, Martin’s examples in his answer - all varying levels of SoC to me. Clearly there’s a relationship to SoC (even Bob himself eludes to it), but like you’ve noted, no amount of rephrasing on my end will lead anywhere with you. – Ash Jun 17 '23 at 03:14
  • 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 '23 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 '23 at 23:12
  • I like very much this idea that basically says it's about the same thing, but SRP is about the practical test to verify soc. Imho, SRP is still too subjective and too often misunderstood, and hence does not add significant benefit over soc. – Christophe Jun 19 '23 at 06:10
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.

JacquesB
  • 57,310
  • 21
  • 127
  • 176
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.

Greg Burghardt
  • 34,276
  • 8
  • 63
  • 114
  • Greg: Thanks. Upvoted. Appreciate you quoting from the referenced article to keep the discussion objective. – Ash Jun 17 '23 at 04: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 '23 at 04: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. – Greg Burghardt Jun 17 '23 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 '23 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. – Greg Burghardt Jun 18 '23 at 12:33
  • I added a little more clarification to the top, and an example at the bottom illustrating where these two concepts do not anyways agree. – Greg Burghardt Jun 18 '23 at 16:41
  • Greg: Thank you, the example is helpful and I can see that you understand my concerns. The example you have provided definitely suggest a circumstance where SRP gets you to a better state than SoC alone. What I'm unsettled about though is that it's an example of doing something badly in order to identify a distinction between the two. I shall continue in my next comment... – Ash Jun 19 '23 at 02:12
  • What you ought to be doing when data access is a factor (regardless of DB or web service as the underlying data store) is using the repository pattern. So there would be an interface `IUserData`. If using a DB, the implementation could be `DBUserData`, which itself could have a dependency on an `IDBMapper`. When switching to a web service, it's not `DBUserData` that would change; instead there'd be a new implementation of `IUserData` - let's call it `WebUserData` (and it may have its own dependencies, separated as required as per SoC). – Ash Jun 19 '23 at 02:19
  • Hence, using the repo pattern, `DBUserData` and the implementation for `IDBMapper` would only change if there's a change in the underlying database, i.e., a change for the same reason (and always the same reason). Yes, SoC here does not get you as good a design as SRP, but that's only if you do not also use the repo pattern...but you really should, and that's what I mean by "doing something badly (to begin with) in order to identify a distinction". – Ash Jun 19 '23 at 02:26
  • Fyi, pretty close to accepting, just need to get over this tiny but significant hurdle. Continuing the discussion using the classes/interfaces I have proposed would go a long way :) – Ash Jun 19 '23 at 02:31
  • @Ash: the repository pattern is not a silver bullet solution for all data access problems. You are getting too hung up on the specific example. – Greg Burghardt Jun 20 '23 at 14:42
  • Greg: I'm accepting. The point I was making above is it's an example of doing something badly to identify a distinction in outcome. Regardless, it is a distinction, which is more than any other answer has been able to provide. SRP really ought to be defined as "Gather together the things that change for the same reasons. Separate those things that change for different reasons", as I feel this is much more applicable as per its wording. I cannot emphasise "as per its wording" enough. This definition also leads to some nice properties, for e.g., the mortar property as you've described it. – Ash Jun 21 '23 at 01:48