213

It seems pretty clear that "Single Responsibility Principle" does not mean "only does one thing." That's what methods are for.

public Interface CustomerCRUD
{
    public void Create(Customer customer);
    public Customer Read(int CustomerID);
    public void Update(Customer customer);
    public void Delete(int CustomerID);
}

Bob Martin says that "classes should have only one reason to change." But that's difficult to wrap your mind around if you're a programmer new to SOLID.

I wrote an answer to another question, where I suggested that responsibilities are like job titles, and danced around the subject by using a restaurant metaphor to illustrate my point. But that still doesn't articulate a set of principles that someone could use to define their classes' responsibilities.

So how do you do it? How do you determine which responsibilities each class should have, and how do you define a responsibility in the context of SRP?

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
  • 28
    Post to [codereview.se] and be ripped apart :-D – Jörg W Mittag Mar 27 '17 at 19:50
  • 8
    @JörgWMittag Hey now, don't scare people away :) – Flambino Mar 27 '17 at 21:56
  • 125
    Questions like this from veteran members demonstrate that the rules and principles we attempt to hold to are by no means *straightforward* or *simple*. They're [sort of] self-contradictory and *mystical* ... as any good set of rules *should* be. And, I'd like to believe questions like this humble the wise, and give hope to those feeling hopelessly stupid. Thanks, Robert! – svidgen Mar 27 '17 at 21:58
  • 3
    Honestly, using metaphores is a very good way. It's basically a natural learning mechanism. I guess the hard part is finding the right one at the right moment. I'm sure there will be one with less ambiguity that we could use often as reference of what SRP means. – Laiv Mar 27 '17 at 22:17
  • 1
    Related: [*How do you determine how coarse or fine-grained a 'responsibility' should be when using the single responsibility principle?*](http://stackoverflow.com/q/2455705/25847) – Mark Rogers Mar 28 '17 at 02:13
  • 9
    For a few weeks now I've been hesitating to ask a reverse question: "What are the clues, that one is splitting single responsibility between too many classes?" I believe that answers too such question could also be useful in answering the OP's question. – Kuba Wyrostek Mar 28 '17 at 10:16
  • @svidgen Really great points. I'm just unsure and curious about _self-contradictory and mystical ... as any good set of rules should be_ . Why is it good that a set of rules are self-contradictory ? – SantiBailors Mar 28 '17 at 11:31
  • Also: [*How do you define a Single Responsibility?*](http://stackoverflow.com/q/246068/25847) – Mark Rogers Mar 28 '17 at 13:54
  • 2
    @SantiBailors Godel's Theorem. (it's *just* a theorem) (Shh! you'll ruin the story) –  Mar 28 '17 at 14:13
  • 1
    I believe SRP is the least generally understood principle of SOLID. It's so difficult to make a good-for-all explanation for everything (unlike let's say the LSP) that this entirely depends on you as a programmer and your experience to judge whether you're not mixing too many different things together. – Andy Mar 28 '17 at 15:14
  • @SantiBailors Well, I probably over-generalized and hyperbolized. But, rules with a self-contradictory or "mystical" smell about them deal with more universal and nuanced subjects. "Don't store your passwords in a plaintext file" is a narrow prohibition, utterly without nuance or mystery. But, a more elemental and universal rule would be, "Keep your password safe." The precise rule is drawn from the "essential" rule by applying it to a particular context by someone who understands the general rule. And the general rule *explains* the precise prohibitions. – svidgen Mar 28 '17 at 15:48
  • 3
    'It seems pretty clear that "Single Responsibility Principle" does not mean "only does one thing."' Actually, that's exactly what it means. The level of abstraction is different between a method, a class, a package, an application, etc. They differ in levels of abstraction. The method is a level of abstraction that does possibly numerous steps to accomplish exactly one thing. Likewise with a class, where the abstraction is "Manage file writing." That's one thing, but requires many methods to accomplish. – Don Branson Mar 28 '17 at 18:47
  • Single responsibility is a scope of the component that is small enough to keep high cohesion. http://www.yegor256.com/2017/03/28/solid.html – Basilevs Mar 29 '17 at 05:06
  • 4
    Possible duplicate of [What are the practical ways to implement the SRP?](http://softwareengineering.stackexchange.com/questions/158845/what-are-the-practical-ways-to-implement-the-srp) – Songo Mar 29 '17 at 09:31
  • You can frame main() as having a single responsibility - the requirements as a whole - yet it is composed from multiple parts that, in a well-designed program, also presumably have single responsibilities (if the principle does not compose in this manner, it is only useful in small cases.) 'Single responsibility' seems to be another way to look at coupling and cohesion. – sdenham Mar 29 '17 at 11:07
  • I have written a blog post about this subject that may interest you https://thetightlycoupled.wordpress.com/2017/02/07/solid-part-1-the-single-responsiblity-principle-srp-may-not-be-what-you-had-in-mind/?frame-nonce=e9c5058617 – Belgi Mar 30 '17 at 14:33
  • 2
    In other words, the very same question, asked by a novice, might have indicated a basic lack of understanding that would be better remedied by doing his own research. When asked by someone with more than 100k rep, one assumes basic understanding already, and so the question is read more profoundly. – rmunn Mar 31 '17 at 03:18
  • 9
    @rmunn: or in other words - big rep attracts even more rep, because nobody cancelled basic human prejudice on stackexchange – Andrejs Mar 31 '17 at 08:27
  • 1
    Comments are not for extended discussion; this conversation has been [moved to chat](http://chat.stackexchange.com/rooms/56470/discussion-on-question-by-robert-harvey-when-using-the-single-responsibility-pri). –  Apr 03 '17 at 02:15
  • @rmunn The level of understanding behind a question cannot be determined from anything except the content of the question or directly knowing the individual. And a low SO rep is just that, low _SO_ rep, which per se says nothing about the skill level. While a high rep might, but it also might just mean that you asked how to declare an array years ago. – SantiBailors Jul 02 '17 at 12:07

16 Answers16

121

One way to wrap your head around this is to imagine potential requirements changes in future projects and ask yourself what you will need to do to make them happen.

For example:

New business requirement: Users located in California get a special discount.

Example of "good" change: I need to modify code in a class that computes discounts.

Example of bad changes: I need to modify code in the User class, and that change will have a cascading effect on other classes that use the User class, including classes that have nothing to do with discounts, e.g. enrollment, enumeration, and management.

Or:

New nonfunctional requirement: We'll start using Oracle instead of SQL Server

Example of good change: Just need to modify a single class in the data access layer that determines how to persist the data in the DTOs.

Bad change: I need to modify all of my business layer classes because they contain SQL Server-specific logic.

The idea is to minimize the footprint of future potential changes, restricting code modifications to one area of code per area of change.

At the very minimum, your classes should separate logical concerns from physical concerns. A great set of examples can be found in the System.IO namespace: there we can find a various kinds of physical streams (e.g. FileStream, MemoryStream, or NetworkStream) and various readers and writers (BinaryWriter, TextWriter) that work on a logical level. By separating them this way, we avoid combinatoric explosion: instead of needing FileStreamTextWriter, FileStreamBinaryWriter, NetworkStreamTextWriter, NetworkStreamBinaryWriter, MemoryStreamTextWriter, and MemoryStreamBinaryWriter, you just hook up the writer and the stream and you can have what you want. Then later on we can add, say, an XmlWriter, without needing to re-implement it for memory, file, and network separately.

John Wu
  • 26,032
  • 10
  • 63
  • 84
  • 37
    While I agree with thinking ahead, there are principles like YAGNI and methodologies like TDD thar suggest the opposite. – Robert Harvey Mar 27 '17 at 19:59
  • 89
    YAGNI tells us not to build stuff we don't need today. It does not tell not to build stuff in a way that is extensible. See also [open/closed principle](https://en.wikipedia.org/wiki/Open/closed_principle), which states "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification." – John Wu Mar 27 '17 at 20:26
  • 20
    @JohnW: +1 for your YAGNI comment alone. I can't believe how much I have to explain to people that YAGNI is *not* an excuse to build a rigid, inflexible system that cannot react to change -- ironically the very opposite of what SRP and the Open/Closed principals are aiming for. – Greg Burghardt Mar 27 '17 at 20:30
  • 3
    @JohnWu And if you don't need Oracle, then the time spent making sure you can easily swap in Oracle is YAGNI. And I don't think I've ever seen that happen. – Andy Mar 27 '17 at 21:14
  • 1
    @Andy: Perhaps not, but what if you need to upgrade from SQL 2008 to SQL 2012? Or switch from SQL authentication to trusted? Both of those are arguments to keep a separate class with the responsibility of *persistence*, rather than, say, having your business objects work with connections directly. – John Wu Mar 27 '17 at 21:16
  • 37
    @JohnWu: I disagree, YAGNI tells us exactly not to build stuff we don't need today. Readability and tests, for example, is something a program always needs "today", so YAGNI is never an excuse not to add structure and injection points. However, as soon as "extensibility" adds significant cost for which the benefits are not obvious "today", YAGNI means to avoid this kind of extensibility, since the latter leads to overengineering. – Doc Brown Mar 27 '17 at 21:18
  • 9
    @JohnWu We did switch from SQL 2008 to 2012. There were a total of two queries that needed changing. And from SQL Auth to trusted? Why would that even be a code change; changing the connectionString in the config file is enough. Again, YAGNI. YAGNI and SRP are sometimes competing concerns, and you need to judge which one has the better cost/benefit. – Andy Mar 27 '17 at 21:23
  • 1
    I wrestled to understand SRP in a way I could articulate for *months.* And to be honest, I think this answer is only helpful to folks who either A) Already understand how to apply SRP at their organization, or B) *Think* they understand SRP, but don't. – svidgen Mar 27 '17 at 21:32
  • 1
    So with SQL backend change, given potential slight differences in syntax, how do you advocate locating queries in a SRP-compliant way? If you only want to change one class when you change backend, naively one might store all the SQL queries in that single class. But with respect to adding features, you're now no longer really SRP-compliant, as you have to change the class which stores the queries *and* the class which implements the functionality. But if your queries are stored on the class which uses them, those would have to change when the backend changes. -- How does one balance this? – R.M. Mar 27 '17 at 21:34
  • Heavens I don't store queries in classes; they belong in stored procedures. I might have to modify a single class if the manner of connection is changed, or if I need to handle some newer exceptions that become possible with the new DB platform. If I hadn't followed SRP, concatenation of the connection string (e.g. if the password isn't stored in cleartext config) may have been implemented in many classes, which would be bad. – John Wu Mar 27 '17 at 21:43
  • 5
    @JohnWu hehe ... so, when you want to change DBMS's ... everyone just quits? or ... what? ... Not saying a DBMS change is a frequent event. Just, in context of this discussion and how much time we all save by adhering religiously to SRP, having all you queries in SP's seems like the worst possible solution if you *are* uncertain about your choice in data providers ... – svidgen Mar 27 '17 at 22:08
  • You'd implement a new class to represent the new persistence method, exposing it using the same interface as the old method. You then modify your composition root to inject the new class instead of the old one. The point is you should not need to modify the classes that work on a logical (not physical) level, which should be all of your business objects, if you followed SRP. – John Wu Mar 27 '17 at 22:11
  • 4
    @JohnWu No, I get that it's easy from the application's perspective. But, who's going to copy all the SP's over? And "upgrade" the syntax? ... Anyway. It was supposed to be a somewhat facetious comment... – svidgen Mar 27 '17 at 22:12
  • If you are migrating data between platforms, you need to migrate the schema, data, constraints, indices, etc. There are tools for that. Migration is a different problem from coding. – John Wu Mar 27 '17 at 22:13
  • 3
    "New business requirement: Users located in California get a special discount." My first thought would be that you should be keeping things like that in some sort of rule-based/logical structure to make updates be database-esque changes rather than internal code changes. Discounts usually don't last forever. – JAB Mar 27 '17 at 23:32
  • @JAB, sure, implement it however you want; the point is the same, the responsibility is *separate*, and shouldn't be in the same class that, say, validates a user's last name. – John Wu Mar 28 '17 at 00:22
  • I've known people to want to swap out Oracle - and not being able too. – Nick Keighley Mar 28 '17 at 10:33
  • 2
    @RobertHarvey These principles are like proverbs - you can usually find one with opposite intent. They are probably best used as prompts to stimulate the dialog (often internal) about the design of your software. – sdenham Mar 29 '17 at 02:17
  • 1
    Would it not be sensible to try and make that case configurable? A table related to locations with a discount amount against it, this can probably be done without code change. (Just a thought) – AJFaraday Mar 29 '17 at 11:19
  • **Please avoid extended discussions in comments. If you would like to discuss the answer further then please visit our chat room. Thank you.** – maple_shaft Mar 29 '17 at 11:58
  • 1
    Eh, I'm not sure if this is exactly the point of single responsibility. "I only will need to change one class" is easily achievable by _mashing up together_ a lot of unrelated stuff on a single class, which directly contradicts SRP. You seem to be talking more abour DI than about SRP. – T. Sar Mar 29 '17 at 17:29
  • 1
    @JohnWu Putting a lot of your logic on SPs is a recipe for a disaster waiting to happen. More often than not, the DB is the most badly versioned and most fragile part of any system. A stored procedure isn't part of the build process, needs a special suit of test cases, and more often than not just adds layers of unneeded complexity to a system. Stick to a good ORM and drop SPs for simple queries for good - save them for complex tasks. – T. Sar Mar 29 '17 at 17:34
  • 3
    The more I think about it, the more I conclude this answer actually got SRP backwards. It is not about "changing just one thing", it is about "having just **one reason** to change". Having a user class that deal with user stuff and nothing is more _good_, having a calculation class that responds to the needs of fifty business rules with different domains isn't. – T. Sar Mar 29 '17 at 17:39
  • I'm not trying to define SRP. My post describes a thought experiment to help guide a developer who is familiar with SRP but is having trouble applying it. I hope it was helpful. – John Wu Mar 29 '17 at 22:13
  • @RobertHarvey Can't disagree with you more. TDD requires you to modularize your code, and in fact I feel it is the best test of SRP. If your tests need to significantly change to accommodate extension, you are doing SRP wrong. – ArTs Mar 30 '17 at 08:18
  • 2
    There is a second reason for YAGNI besides just that you may never need the additional functionality. That reason is that without a current use case you are solving, you are just guessing how the functionality should work. For the "swap out provider" issue, I once worked on a project where a payment provider interaction was hypothetically abstracted away; but then when we went to switch providers, our guesses at where the abstraction points needed to be were 90% wrong, so it was still a bunch of work (and the original efforts were wasted). – stannius Mar 30 '17 at 18:45
  • 1
    @stannius: *Don't* design around future, unknown interfaces; *do* design in a manner that allows for their future introduction. – John Wu Mar 31 '17 at 17:03
78

Practically speaking, responsibilities are bounded by those things that are likely to change. Thus, there's no scientific or formulaic way to arrive at what constitutes a responsibility, unfortunately. It's a judgement call.

It's about what, in your experience, is likely to change.

We tend to apply the language of the principle in a hyperbolic, literal, zealous rage. We tend to split classes because they could change, or along lines that simply help us break problems down. (The latter reason isn't inherently bad.) But, the SRP does not exist for its own sake; it's in service to creating maintainable software.

So again, if the divisions aren't driven by likely changes, they're not truly in service to the SRP1 if YAGNI is more applicable. Both serve the same ultimate goal. And both are matters of judgement--hopefully seasoned judgement.

When Uncle Bob writes about this, he suggests that we think of "responsibility" in terms of "who's asking for the change." In other words, we don't want Party A to lose their jobs because Party B asked for a change.

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, or rather, a single tightly coupled group of people representing a single narrowly defined business function. You want to isolate your modules from the complexities of the organization as a whole, and design your systems such that each module is responsible (responds to) the needs of just that one business function. (Uncle Bob - The Single Responsibility Principle )

Good and experienced developers will have a sense for which changes are likely. And that mental list will vary somewhat by industry and organization.

What constitutes a responsibility in your particular application, at your particular organization, is ultimately a matter of seasoned judgement. It's about what's likely to change. And, in a sense, it's about who owns the module's internal logic.


1. To be clear, that doesn't mean they're bad divisions. They could be great divisions that improve code readability dramatically. It just means they're not driven by the SRP.

svidgen
  • 13,414
  • 2
  • 34
  • 60
  • 11
    Best answer, and actually quotes Uncle Bob's thoughts. As for what is likely to change, everybody makes a big deal over I/O, "what if we change the database?" or "what if we switch from XML to JSON?" I think this is *usually* misguided. The real question should be "what if we need to change this int to a float, add a field, and change this String to a List of Strings?" – user949300 Mar 28 '17 at 01:07
  • 1
    But in order to do Unit Test and Dependency Inversion you're going to abstract your i/o and persistence away anyway. – Nick Keighley Mar 28 '17 at 10:41
  • 2
    This is cheating. Single responsibility by itself is just a proposed way of "change isolation". Explaining, that you need to isolate changes to keep responsibility "single", does not suggest how to do this, just explains the origin of the requirement. – Basilevs Mar 28 '17 at 12:40
  • 6
    @Basilevs I'm trying to hone in the on the deficiency you're seeing in this answer--not to mention Uncle Bob's answer! But, perhaps I need to clarify that SRP is *not* about ensuring that "a change" will impact only 1 class. It's about ensuring that each class will respond to only "one change." ... It's about trying to draw your arrows from each class to a single owner. Not from each owner to a single class. – svidgen Mar 28 '17 at 13:50
  • 1
    I want to know Uncle Bob's thoughts. All the rest are details. –  Mar 28 '17 at 14:22
  • 1
    *It's about trying to draw your arrows from each class to a single owner. Not from each owner to a single class.* - svidgen. Please allow me to steal this quote. – Chris Wohlert Mar 29 '17 at 11:52
  • @chris If you think it's worth stealing, go for it! – svidgen Mar 29 '17 at 13:33
  • 2
    Thank you for providing a pragmatic response! Even Uncle Bob warns against zealous adherence to SOLID principles in _Agile Architecture_. I don't have the quote handy, but he basically says that splitting responsibilities inherently increases the level of abstraction in your code and that all abstraction comes at a cost, so make sure the _benefit_ of following SRP (or other principles) outweighs the _cost_ of adding more abstraction. _(cont'd next comment)_ – Michael L. Mar 29 '17 at 15:16
  • 4
    This is why we _should_ put the product in front of the customer as early and as often as is reasonable, so they will force changes in our design and we can see what areas are _likely_ to change in that product. Additionally, he warns that we can't protect ourselves from _every_ kind of change. For _any_ application, certain kinds of changes will be difficult to make. We need to make sure those are the changes which are least likely to happen. – Michael L. Mar 29 '17 at 15:16
  • 1
    @MichaelL. Thanks for your comment. Good insight about getting products in front of customers sooner rather than later with regards to applying SRP right. – svidgen Mar 29 '17 at 15:24
  • @nocomprende IMHO the link to Uncle Bob's thoughts above is what often passes for "thought" when software design methodology gets confused with religion. The example is so trivial it teaches nothing. I mean, it's self-evident that a class with the single responsibility of "creating enterprise-level factories which create application-specific factories which create classes" is always a Good Thing for promoting Bug-Free Software Design, yes? – alephzero Mar 30 '17 at 11:30
  • @alephzero it is not evident to my self. I just thought the paraphrase of Einstein was funny. Apparently no one else did? –  Mar 30 '17 at 12:16
  • @nocomprende I didn't even recognize the quote! ... Had I, I'd have definitely given you a "lol" or something. – svidgen Mar 30 '17 at 14:01
31

I follow "classes should have only one reason to change".

For me, this means thinking of harebrained schemes that my product owner might come up with ("We need to support mobile!", "We need to go to the cloud!", "We need to support Chinese!"). Good designs will limit the impact of these schemes to smaller areas, and make them relatively easy to accomplish. Bad designs mean going to a lot of code and making a bunch of risky changes.

Experience is the only thing I've found to properly evaluate the likelihood of those crazy schemes - because making one easy might make two others harder - and evaluating the goodness of a design. Experienced programmers can imagine what they'd need to do to change the code, what is lying around to bite them in the ass, and what tricks make things easy. Experienced programmers have a good gut feel for how screwed they are when the product owner asks for crazy stuff.

Practically, I find that unit tests help here. If your code is inflexible, it's going to be hard to test. If you can't inject mocks or other test data, you're probably not going to be able to inject that SupportChinese code.

Another rough metric is the elevator pitch. Traditional elevator pitches are "if you were in an elevator with an investor, can you sell him on an idea?". Startups need to have simple, short descriptions of what they're doing - what their focus is. Likewise, classes (and functions) should have a simple description of what they do. Not "this class implements some fubar such that you can use it in these specific scenarios". Something you can tell another developer: "This class creates users". If you can't communicate that to other developers, you're going to get bugs.

Telastyn
  • 108,850
  • 29
  • 239
  • 365
  • Sometimes you go to implement what you thought would be a messy change, and it turns out simple, or a small refactor makes it simple, and adds useful functionality at the same time. But yeah, usually you can see trouble coming. –  Mar 28 '17 at 14:24
  • 17
    I'm a big advocate for the "elevator pitch" idea. If it's hard to explain what a class does in a sentence or two you are in risky territory. – Ivan Mar 28 '17 at 14:55
  • 1
    You touch on an important point: the likelihood of those crazy schemes varies dramatically from one project owner to the next. You have to rely not only on your general experience, but on how well you know the project owner. I've worked for people who wanted to cut our sprints down to one week, and *still* couldn't avoid changing direction mid-sprint. – Kevin Krumwiede Mar 28 '17 at 16:41
  • 2
    In addition to the obvious benefits, documenting your code using "elevator pitches" also serves to help you think of what your code is doing using natural language which I find useful in uncovering multiple responsibilities. – Alexander Mar 28 '17 at 19:28
  • 1
    @KevinKrumwiede That is what the "Chicken running around with its head cut off" and "Wild Goose Chase" methodologies are for! –  Mar 28 '17 at 23:00
29

No one knows. Or at least, we are unable to agree on one definition. That is what makes SPR (and other SOLID principles) quite controversial.

I would argue being able to figure out what is or isn't a responsibility is one of the skills software developer has to learn over course of his career. The more code you write and review, the more experience you will have to determine if something is single or multiple responsibilities. Or if single responsibility is fractured across separate parts of the code.

I would argue that primary purpose of SRP is not to be hard rule. It is to remind us to be mindful of cohesion in code and to always put some conscious effort into determining what code is cohesive and what isn't.

Euphoric
  • 36,735
  • 6
  • 78
  • 110
  • 21
    New programmers do have a tendency to treat SOLID as if it were a set of laws, which it isn't. It's merely a grouping of good ideas to help people get better at class design. Alas, people tend to take these principles far too seriously; I recently saw a job posting that cited SOLID as one of the job requirements. – Robert Harvey Mar 27 '17 at 22:40
  • 10
    +42 for the last paragraph. As @RobertHarvey says, things like SPR, SOLID and YAGNI should not be taken as "_absolute rules_", but as general principals of "good advice". Between them (and others) the advice will sometimes be contradictory, but balancing that advice (as opposed to following a rigid set of rules) will (over time, as your experience grows) guide you to produce better software. There should be only one "absolute rule" in software development: "_There are no absolute rules_". – TripeHound Mar 28 '17 at 11:11
  • This is very good clarification on one aspect of SRP. But, even if the SOLID principles are not hard rules, they're not terribly valuable if no one understands what they mean--even less so if your claim that "no one knows" is actually true! ... it makes sense for them to be hard to understand. As with any skill, there's *something* that distinguishes the good from the *less* good! But ... "no one knows" makes it more of a hazing ritual. (And I don't believe that's the intent of SOLID!) – svidgen Mar 28 '17 at 14:10
  • 3
    By "No one knows", I hope @Euphoric simply means that there is no _precise_ definition that will work for every use case. It is something that requires a degree of judgment. I think one of the best ways to determine where your responsibilities lay is to iterate rapidly and _let your codebase tell you_. Look for "smells" that your code is not easily maintainable. For instance, when a change to a single business rule starts having cascading affects through seemingly unrelated classes, you probably have a violation of SRP. – Michael L. Mar 29 '17 at 15:29
  • 2
    I heartily second @TripeHound and others who have pointed out that all these "rules" don't exist to define the One True Religion of development, but to increase the likelihood of developing maintainable software. Be very wary of following a "best practice" if you can't explain how it promotes maintainable software, improves quality or increases efficiency of development.. – Michael L. Mar 29 '17 at 15:32
6

In this conference at Yale, Uncle Bob gives this funny example:

Enter image description here

He says that Employee has three reasons to change, three sources of change requirements, and gives this humorous and tongue-in-cheek, but illustrative nonetheless, explanation:

  • If the CalcPay() method has an error and costs the company millions of US$, the CFO will fire you.

  • If the ReportHours() method has an error and costs the company millions of US$, the COO will fire you.

  • If the WriteEmmployee() method has an error that causes the erasure of a lot of data and costs the company millions of US$, the CTO will fire you.

So having three different C level execs potentially firing you for costly errors in the the same class means the class has too many responsibilities.

He gives this solution that solves the violation of SRP, but yet has to solve the violation of DIP which is not shown in the video.

Enter image description here

Peter Mortensen
  • 1,050
  • 2
  • 12
  • 14
Tulains Córdova
  • 39,201
  • 12
  • 97
  • 154
  • This example looks more like a class that has the *wrong* responsibilities. – Robert Harvey Mar 28 '17 at 23:50
  • 4
    @RobertHarvey When a class has too many responsabilities, it means that the extra responsabilities are the _wrong_ responsabilities. – Tulains Córdova Mar 28 '17 at 23:54
  • 6
    I hear what you're saying, but I don't find it compelling. There's a difference between a class having too many responsibilities and a class doing something it has no business doing at all. It may sound the same, but it isn't; counting peanuts isn't the same as calling them walnuts. It's Uncle Bob's principle and Uncle Bob's example, but if it were sufficiently descriptive, we wouldn't need this question at all. – Robert Harvey Mar 28 '17 at 23:57
  • @RobertHarvey, what is the difference? Those situations seem isomorphic to me. – Paul Draper Apr 01 '17 at 18:45
5

I think the term "responsibility" is useful as a metaphor because it permits us to use the software to investigate how well the software is organized. In particular, I'd focus on two principles:

  • Responsibility is commensurate with authority.
  • No two entities should be responsible for the same thing.

These two principles let us dole out responsibility meaningfully because they play off each other. If you are empowering a piece of code to do something for you, it needs to have a responsibility for what it does. This causes the responsibility that a class might have to grow, expanding it's "one reason to change" to wider and wider scopes. However, as you make things wider, you naturally start to run into situations where multiple entities are responsible for the same thing. This is fraught with issues in real life responsibility, so surely it is an issue in coding as well. As a result, this principle causes scopes to narrow, as you subdivide the responsibility into un-duplicated parcels.

In addition to these two, a third principle seems reasonable:

  • Responsibility can be delegated

Consider a freshly minted program... a blank slate. At first, you only have one entity, which is the program as a whole. It is responsible for... everything. Naturally at some point you will start delegating responsibility to functions or classes. At this point, the first two rules come into play forcing you to balance that responsibility. The top level program is still responsible for the overall output, just like a manager is responsible for the productivity of their team, but each sub-entity has been delegated responsibility, and with it the authority to carry out that responsibility.

As an added bonus, this makes SOLID particularly compatible with any corporate software development one might need to do. Every company on the planet has some concept of how to delegate responsibility, and they don't all agree. If you delegate responsibility within your software in a way which is reminiscent of your company's own delegation, it's going to be much easier for future developers to come up to speed with how you do things at this company.

Cort Ammon
  • 10,840
  • 3
  • 23
  • 32
  • I'm not 100% sure this fully explains it. But, I think explaining "responsibility" with respect to "authority" is an insightful way to phrase it! (+1) – svidgen Mar 28 '17 at 21:21
  • Pirsig said, "You tend to build your problems in to the machine", which gives me pause. –  Mar 28 '17 at 23:03
  • @nocomprende You also tend to build your strengths into the machine. I'd argue that when your strengths and your weakness are the same things, that's when it gets interesting. – Cort Ammon Mar 28 '17 at 23:13
4

SRP is hard to get right. It is mostly a matter of assigning 'jobs' to your code and making sure each part has clear responsibilities. Like in real life, in some cases splitting the work among people can be quite natural, but in other cases it may be really tricky, especially if you do not know them (or the work).

I always recommend you just write simple code that works first, then refactor a little: You will tend to see how the code clusters naturally after a while. I think it is a mistake to force responsibilities before you know the code (or people) and the work to be done.

One thing you will notice is when the module starts doing too much and is hard to debug/maintain. This is the moment to refactor; what should the core job be and what tasks could be given to another module? For example, should it handle security checks and the other work, or should you do security checks elsewhere first, or will this make the code more complex?

Use too many indirections and it becomes a mess again ... as for other principles, this one will be in conflict with others, like KISS, YAGNI, etc. Everything is a matter of balance.

Peter Mortensen
  • 1,050
  • 2
  • 12
  • 14
3

I think a better way of subdividing things than "reasons to change" is to start by thinking in terms of whether it would make sense to require that code which needs to do perform two (or more) actions should need to hold a separate object reference for each action, and whether it would be useful to have a public object which could do one action but not the other.

If the answers to both questions are yes, that would suggest the actions should be done by separate classes. If the answers to both questions are no, that would suggest that from a public standpoint there should be one class; if the code for that would be unwieldy, it may be internally subdivided into private classes. If the answer to the first question is no, but the second is yes, there should be a separate class for each action plus a composite class which includes references to instances of the others.

If one has separate classes for a cash register's keypad, beeper, numeric readout, receipt printer, and cash drawer, and no composite class for a complete cash register, then code which is supposed to process a transaction might end up accidentally getting invoked in a way that takes input from one machine's keypad, produces noise from a second machine's beeper, shows numbers on a third machine's display, prints a receipt on a fourth machine's printer, and pops a fifth machine's cash drawer. Each of those sub functions might usefully be handled by a separate class, but there should also be a composite class which joins them. The composite class should delegate as much logic to the constituent classes as possible, but should when practical wrap functions of its constituent components rather than requiring client code to access the constituents directly.

One could say that each class's "responsibility" is either to incorporate some real logic or else to provide a common attachment point for multiple other classes that do so, but what's important is to focus first and foremost on how client code should view a class. If it makes sense for client code to see something as a single object, then client code should see it as a single object.

supercat
  • 8,335
  • 22
  • 28
  • This is sound advice. It might be worth pointing out that you divide responsibilities according to more criteria than only the SRP. – Jørgen Fogh Mar 28 '17 at 12:18
  • 1
    Car analogy: I don't need to know how much gas is in someone else's tank, or want to turn someone else's wipers on. (but that's the definition of the internet) (Shh! you'll ruin the story) –  Mar 28 '17 at 14:29
  • 1
    @nocomprende - "I don't need to know how much gas is in someone else's tank," - unless you are a teenager trying to decide which of the family's cars to "borrow" for your next trip... ;) – alephzero Mar 30 '17 at 11:33
3

"Single responsibility principle" is perhaps a confusing name. "Only one reason to change" is a better description of the principle, but is still easy to misunderstand. We are not talking about say what causes objects to change state at runtime. We are taking about what could cause developers to have to change the code in the future.

Unless we are fixing a bug, the change will be due to a new or changed business requirement. You will have to think outside the code itself, and imagine what outside factors might cause requirements to change independently. Say:

  • Tax rates change due to a political decision.
  • Marketing decides to change names of all products
  • UI has to be redesigned to be accessible
  • The database is congested, so you need to do some optimizations
  • You have to accommodate a mobile app
  • and so on...

Ideally you want independent factors to affect different classes. E.g. since tax rates change independent of product names, changes should not affect the same classes. Otherwise you run the risk of a tax change introduction an error in product naming, which is the kind of tight-coupling you want to avoid with a modular system.

So don't just focus of what could change - anything might conceivably change in the future. Focus on what might independently change. Changes are typically independent if they are caused by different actors.

Your example with job titles is on the right track, but you should take it more literally! If marketing might cause changes to the code and finance might cause other changes, these changes should not affect the same code, since these are literally different job titles and therefore changes will happen independently.

To quote Uncle Bob who invented the term:

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, or rather, a single tightly coupled group of people representing a single narrowly defined business function. You want to isolate your modules from the complexities of the organization as a whole, and design your systems such that each module is responsible (responds to) the needs of just that one business function.

So to sum up: A "responsibility" is catering to a single business function. If more than one actor could cause you to have to change a class, then the class probably breaks this principle.

JacquesB
  • 57,310
  • 21
  • 127
  • 176
  • According to his book "Clean Architecture" this is exactly right. Business rules should come from one source, and once source only. This means that HR, Operations and IT all need to cooperate formulating requirements in a "Single Responsibility". And that is the principle. +1 – Benny Skogberg Jul 23 '18 at 19:46
2

A good article that explains the SOLID programming principles and gives examples of code both following and not following this principles is https://scotch.io/bar-talk/s-o-l-i-d-the-first-five-principles-of-object-oriented-design.

In the example relating to SRP he gives an example of a few shape classes (circle and square) and a class designed to calculate the total area of multiple shapes.

In his first example he creates the area calculating class and has it return his output as HTML. Later on he decides he wants to display it as JSON instead and has to change his area calculating class.

The problem with this example is that his area calculating class is responsible for calculating the area of shapes AND displaying that area. He then goes through a better way to do this using another class designed specifically for displaying areas.

This is a simple example (and more easily understood reading the article as it has code snippets) but demonstrates the core idea of SRP.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
blitz1616
  • 129
  • 2
0

First of all, what you have is actually two separate problems: the problem of what methods to put in your classes, and the problem of interface bloat.

Interfaces

You have this interface:

public Interface CustomerCRUD
{
  public void Create(Customer customer);
  public Customer Read(int CustomerID);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

Presumable, you have multiple classes that conform to the CustomerCRUD interface (otherwise an interface is unnecessary), and some function do_crud(customer: CustomerCRUD) that takes in a conforming object. But you have already broken the SRP: you have tied these four distinct operations together.

Let's say that later on you would to operate on database views. A database view has only the Read method available for it. But you want to write a function do_query_stuff(customer: ???) that operators transparently on either full-blown tables or views; it only uses the Read method, after all.

So create an interface

public Interface CustomerReader { public Customer Read(customerID: int) }

and factor your CustomerCrud interface as:

public interface CustomerCRUD extends CustomerReader
{
  public void Create(Customer customer);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

But there's no end in sight. There could be objects which we can create but not update, etc. This rabbit hole is too deep. The only sane way to adhere to the single responsibility principle is to make all your interfaces contain exactly one method. Go actually follows this methodology from what I've seen, with the vast majority of interfaces containing a single function; if you want to specify an interface that contains two functions, you have to awkwardly create a new interface that combines the two. You soon get a combinatorial explosion of interfaces.

The way out of this mess is to use structural subtyping (implemented in e.g. OCaml) instead of interfaces (which are a form of nominal subtyping). We don't define interfaces; instead, we can simply write a function

let do_customer_stuff customer = customer.read ... customer.update ...

that calls whatever methods we like. OCaml will use type inference to determine that we can pass in any object that implements these methods. In this example, it would be determine that customer has type <read: int -> unit, update: int -> unit, ...>.

Classes

This solves the interface mess; but we still have to implement classes that contain multiple methods. For example, should we create two different classes, CustomerReader and CustomerWriter? What if we want to change how tables are read (e.g. we now cache our responses in redis before after fetching the data), but now how they're written? If you follow this chain of reasoning to its logical conclusion, you're lead inextricably to functional programming :)

gardenhead
  • 4,719
  • 2
  • 19
  • 24
0

In my mind, the closest thing to a SRP that comes to my mind is a usage flow. If you don't have a clear usage flow for any given class it's probably your class has a design smell.

A usage flow would be a given method call succession that would give you an expected (thus testable) result. You basically define a class with the use cases it got IMHO, that's why all program methodology focuses on interfaces over implementation.

Diane M
  • 2,046
  • 9
  • 14
0

It is to achieve that multiple requirement changes, does not require your component to change.

But good luck understanding that at first glance, when you first hear about SOLID.


I see a lot of comments saying SRP and YAGNI can contradict eachother, but YAGNI enforced by TDD (GOOS, London School) taught me to think of and design my components from a client's perspective. I started designing my interfaces by what is the least any client would want it to do, that is how little it should do. And that exercise can be done without any knowledge of TDD.

I like the technique described by Uncle Bob (I cannot remember from where, sadly), which goes something like:

Ask yourself, what does this class do?

Did your answer contain either of And or Or

If so, extract that part of the answer, it a responsibility of its own

This technique is an absolute, and as @svidgen said, SRP is a judgement call, but when learning something new, absolutes are the best, it is easier to just always do something. Make sure the reason you don't separate is; an educated estimation, and not because you don't know how to. This is the art, and it takes experience.


I think a lot of the answers seem to make an argument for decoupling when talking about SRP.

SRP is not to make sure a change doesn't propagate down the dependency graph.

Theoretically, without SRP, you wouldn't have any dependecies...

One change should not cause a change many places in the application, but we got other principles for that. SRP does however, improve the Open Closed Principle. This principle is more about abstraction, however, smaller abstractions are easier to reimplement.

So when teaching SOLID as a whole, be careful to teach that SRP allows you to change less code when requirements change, when in fact, it allows you to write less new code.

Chris Wohlert
  • 529
  • 3
  • 15
  • 3
    `When learning something new, absolutes are the best, it is easier to just always do something.` -- In my experience, new programmers are far too dogmatic. Absolutism lead to non-thinking developers and cargo-cult programming. Saying "just do this" is fine, so long as you understand that the person you are speaking to will have to later *unlearn* what you have taught them. – Robert Harvey Mar 29 '17 at 14:45
  • @RobertHarvey, Completely true it creates dogmatic behavior, and you have to *unlearn/relearn* as you gain experience. This is my point though. If a new programmer tries to make judgement calls without any way to reason their decision it seems borderline useless, because they don't know why it worked, when it worked. By making people *overdo* it, it teaches them to look for the exceptions instead of making unqualified guesses. Everything you said is about absolutism is correct, which is why it should only be a starting point. – Chris Wohlert Mar 30 '17 at 12:23
  • @RobertHarvey, A quick *real life* example: You might teach your children to **always** be honest, but as they grow older, they will probably realize a few exceptions where people don't want to hear their most honest thoughts. To expect a 5 year old to make a correct judgement call about being honest is optimistic at best. :) – Chris Wohlert Mar 30 '17 at 12:26
0

There is no clear answer to that. Although the question is narrow, the explanations aren't.

For me, it is something like Occam's Razor if you want to. It is an ideal where I try to measure my current code against. It is hard to nail it down in plain and simple words. Another metaphor would be »one topic« which is as abstract, i.e. hard to grasp, as »single responsibility«. A third descripton would be »dealing with one level of abstraction«.

What does that mean practically?

Lately I use a style of coding which consists mostly of two phases:

Phase I is best described as creative chaos. In this phase I write down code as thoughts are flowing - i.e. raw and ugly.

Phase II is the complete opposite. It is like cleaning up after a hurricane. This takes the most work and discipline. And then I look at the code from a designer's perspective.

I am working mostly in Python now, which allows me to think of objects and classes later. First Phase I - I write only functions and spread them almost by random in different modules. In Phase II, after I got things going, I have a closer look at what module deals with which part of the solution. And while skimming through the modules, topics are emergent to me. Some functions are thematically related. These are good candidates for classes. And after I turned functions into classes - which is nearly done with indentation and adding self to the parameter list in python ;) - I use SRP like Occam's Razor to strip out functionality to other modules and classes.

A current example may be writing small export functionality the other day.

There was the need for csv, excel and combined excel sheets in a zip.

The plain functionality was each done in three views (=functions). Each function used a common method for determining filters and a second method to retrieve the data. Then in each function the preparation of the export took place and was delivered as a Response from the server.

There were too many levels of abstraction mixed up:

I) dealing with incoming/outgoing request/response

II) determining filters

III) retrieving data

IV) transformation of data

The easy step was to use one abstraction (exporter) to deal with the layers II-IV in a first step.

The only remain was the topic dealing with requests/responses. At the same level of abstraction is extracting request parameters which is okay. So I had for this view one "responsibility".

Second, I had to break up the exporter, which as we saw consisted of at least three other layers of abstraction.

Determining filter criteria and actual retrival are nearly on the same level of abstraction (the filters are needed to get the right subset of the data). These levels were put into something like an data access layer.

In the next step I broke the actual export mechanisms apart: Where writing to a temporal file was needed, I broke that into two "responsibilities": one for the actual writing of the data to disk and another part which dealt with the actual format.

Along the forming of the classes and modules, things became clearer, what belonged where. And always the latent question, whether the class does too much.

How do you determine which responsibilities each class should have, and how do you define a responsibility in the context of SRP?

It is hard to give a recipe to follow. Of course I could repeat the cryptic »one level of abstraction« - rule if that helps.

Mostly for me it is a kind of "artistic intuition" which leads to the current design; I model code like an artist may sculpt clay or does painting.

Imagine me as a Coding Bob Ross ;)

Thomas Junk
  • 9,405
  • 2
  • 22
  • 45
0

What I try to do to write code that follows the SRP:

  • Choose a specific problem you need to solve;
  • Write code that solves it, write everything in one method (eg: main);
  • Carefully analyze the code and, based on the business, try to define the responsibilities that are visible in all operations that are being performed (this is the subjective part that also depends on the business/project/customer);
  • Please, note that all the functionality is already implemented; what's next is only organization of the code (no additional feature or mechanism will be implemented from now on in this approach);
  • Based on the responsibilities that you defined in previous steps (that are defined based on the business and the "one reason to change" idea), extract a separate class or method for each one;
  • Please note that this approach only cares about the SPR; ideally there should be additional steps here trying to adhere to the other principles as well.

Example:

Problem: get two numbers from the user, calculate their sum and output the result to the user:

//first step: solve the problem right away
static void Main(string[] args)
{
    Console.WriteLine("Number 1: ");
    int firstNumber = Convert.ToInt32(Console.ReadLine());

    Console.WriteLine("Number 2: ");
    int secondNumber = Convert.ToInt32(Console.ReadLine());

    int result = firstNumber + secondNumber;

    Console.WriteLine("Hi there! The result is: {0}", result);

    Console.ReadLine();
}

Next, try to define responsibilities based on tasks that need to be performed. From this, extract the appropriate classes:

//Responsible for getting two integers from the user
class Input {
    public int FirstNumber { get; set; }
    public int SecondNumber { get; set; }
    public void Read() {
        Console.WriteLine("Number 1: ");
        FirstNumber = Convert.ToInt32(Console.ReadLine());

        Console.WriteLine("Number 2: ");
        SecondNumber = Convert.ToInt32(Console.ReadLine());
    }
}

//Responsible for calculating the sum of two integers
class SumOperation {
    public int Result { get; set; }
    public void Calculate(int a, int b) {
        Result = a + b;
    }
}

//Responsible for the output of some value to the user
class Output {
    public void Write(int result) {
        Console.WriteLine("Hello! The result is: {0}", result);
    }
}

Then, the refactored program becomes:

//Program: responsible for main execution.
//Gets two numbers from user and output their sum.
static void Main(string[] args)
{
    var input = new Input();
    input.Read();

    var operation = new SumOperation();
    operation.Calculate(input.FirstNumber, input.SecondNumber);

    var output = new Output();
    output.Write(operation.Result);

    Console.ReadLine();
}

Note: this very simple example takes into consideration only the SRP principle. The usage of the other principles (eg: the "L" -- code should depend on abstractions rather than concretions) would provide more benefits to the code and make it more maintainable for business changes.

Emerson Cardoso
  • 2,050
  • 7
  • 14
  • 2
    Your example is too simple to adequately illustrate SRP. Nobody would do this in real life. – Robert Harvey Oct 04 '17 at 15:06
  • Yes, in real projects I write some pseudocode rather than writing the the exact code like in my example. After the pseudocode, I try to split the responsibilities just like I did in the example. Anyway, that's just how I do it. – Emerson Cardoso Oct 04 '17 at 16:39
0

From Robert C. Martins book Clean Architecture: A Craftsman's Guide to Software Structure and Design, published September 10 2017, Robert writes on page 62 the following:

Historically, the SRP has been described this way:

A module should have one, and only one, reason to change

Software systems are changed to satisfy users and stakeholders; those users and stakeholders are the "reason to change". that the principle is talking about. Indeed, we can rephrase the principle to say this:

A module should be responsible to one, and only one, user or stakeholder

Unfortunately, the word "user" and "stakeholder" aren't really the right word to use here. There will likely be more than one user or stakeholder who wants the system changed in the sane way. Instead we're really referring to a group - one or more people who require that change. We'll refer to that group as an actor.

Thus the final version of the SRP is:

A module should be responsible to one, and only one, actor.

So this is not about code. The SRP is about controlling the flow of requirements and business needs, which can only come from one soure.

Benny Skogberg
  • 370
  • 5
  • 20
  • I'm not sure why you're making the distinction that "it's not about code." Of course it's about code; this is software development. – Robert Harvey Jul 23 '18 at 20:15
  • @RobertHarvey My point is that the flow of requirements comes from one source, the actor. Users and Stakeholders are not into code, they are into business rules which come to us as requirements. So the SRP is a process to control these requirements, which to me isn't code. It's Software Development (!), but not code. – Benny Skogberg Jul 23 '18 at 20:20