The Misunderstood Single Responsibility Principle

The Single Responsibility Principle (SRP) is the first of the SOLID design principles that have been highly influential in software engineering since they were introduced by Robert Martin ("Uncle Bob") in 2000.  Unfortunately, this particular principle is often misunderstood.  When coupled with blind faith this can engender overly simplistic thinking and design mistakes.

The principle has often been framed as "each software module should have one and only one reason to change".  And then the name seems to suggest that the module should have a "single responsibility".  That seems pretty easy to understand.  So the module should only do one thing, right?  No.  There's a different rule of thumb around that: a function should only do one thing.  Have you ever held your nose while naming a function something like "ImportDataAndLoadIntoDatabase".  We know this: a function should have a single, well, function.

The SRP is a license to sometimes forgive yourself for what feels like breaking cohesion.  The principle actually means that the code in a given module has to be owned by one and only one ultimate business owner (or functional unit).  If that isn't true you have to break it up.  Uncle Bob uses an example (in writing and talks) where the CFO and COO both depend on the code that calculates employee hours.  Calculating hours is simple math that everyone should agree on, right?  One executive requests a change to that code and it breaks the other's business rule.  The hours calculation cannot be shared: that piece of code should have a Finance version and an Operations version, despite the fact that that violates our DRY and cohesion sensibilities.  This is very different than the way I have seen most people apply the SRP.

While I don't need to apply it everyday, the SRP does align with my experience.  I worked on a Transportation Management System and we had an object that represented truck movements.  It was operational in nature - you'd assign a driver, arrival and departure times for shipping locations, etc.  There was another version of that object that was used for paying the drivers.  The objects were one-to-one with a lot of apparent duplication.  It really drove me crazy!  Several times I tried to treat them uniformly with the same code and it inevitably failed.  And guess what?  One object belonged to Operations and one belonged to (you guessed it) Finance.  It was the exact thing that Uncle Bob was talking about.  While the SRP is true, it's sometimes hard to apply proactively as who (or which group) is the "ultimate owner" of something can be squishy and take time to become clear, especially if you're building a system from scratch with many stakeholders.  From another angle, I see the SRP as a natural consequence of DDD principles: it's unsafe to have a model that is shared across bounded contexts.

Unfortunately, I've seen many people follow the incorrect understanding of the SRP with blind faith.  Often people who like tiny classes will use it to justify making many more even tinier classes.  What starts as a simple, easy-to-understand class ends up being broken into many abstractions that are now harder to understand collectively.  Regardless of whether you like that style, the SRP on its own will not drive you there.  I recently read Adaptive Code (2nd Edition, Microsoft Press) which, as of today, has a 4.7/5 rating on Amazon with 123 ratings.  There is a whole chapter on the SRP.  And throughout, it incorrectly explains the SRP as relating to classes doing too many things and justifies design decisions based on that alone.  For cohesion reasons, classes shouldn't do too many things; so the design decisions aren't always bad.  But, as an example, a class with 2 pages of code is broken into ~12 classes and interfaces, all in the name of the misunderstood SRP.  And this is a well-respected book by an an accomplished author, not some random blog post.

We have to encourage emerging engineers to understand the reasoning behind these principles, to wrestle with them, to apply them when they make sense and reject them when they don't.  It's also instructive and fascinating to go back to the sources.  The SOLID principles are rooted in core design principles that pre-date our generation -- like the Parnas paper that introduces the idea of information-hiding and the book where Constantine introduces the ideas of coupling and cohesion.  Uncle Bob credits these works with influencing his thinking.  In his talks, usually after his requisite, random physics ramblings, he always makes an effort to connect the present with the past.  That's a great thing.  But there aren't many Uncle Bobs around.  As an industry, we don't do a good job of passing down experience across generations.  What can we do about that?  While it's an exaggeration to say that "there is nothing new under the sun", it's amazing how often we wrestle with old problems and are unaware that we can stand on the shoulders of those that came before us.

Why do we as engineers like these design principles? (I know I do.)  Aside from their heuristic utility, I think it's because they provide what feel like constraints for what would otherwise be an unbounded solution space.  They reduce anxiety and introduce some semblance of determinism where none can be found.  Ordinarily we're a rebellious lot and hate constraints (tell someone they must use vim or emacs).  But constraints are our friend when we're facing the abyss of an open-ended problem so we reach for them.  But we sometimes reach too far.  Experience-based observations become principles, which become rules of thumb, which become rules proper that, finally, ossify into law.  But let's be careful here.  Physics aside, the only absolute law I've found in Software Engineering is that there are no laws to be found.  After all, we're building castles out of bits.

Comments

  1. Well, but then - who's fault is it that the principle is so widely misunderstood? Maybe the definitions, explanations and examples should be more clear? By the way, with all due respect, I don't understand your definition of SRP presented in the article... like... at all.

    ReplyDelete
    Replies
    1. Who's "fault" it is is not terribly interesting/relevant to me. I was just pointing out that people often mis-apply the SRP, according to its latest definition anyway. The explanation of it was abbreviated. The post that it links to (from Bob) explains it in more detail: https://blog.cleancoder.com/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html. Hope that helps.

      Delete
  2. This is a balanced review of the SRP and it demonstrates your maturity in applying software design principles. I could have loved to see how you reviewed SRP with respect to other SOLID principles. I always find it useful when I look at these principles holistically. May be an idea for your next blog post.

    ReplyDelete
  3. Excellent read and something I have tried to explain to people many times. It is a tricky thing to explain but I think this article does a pretty good job of it. Single responsibility does not mean do one thing, it means the code should be responsible to a single owner, aka a single point of change.

    ReplyDelete
  4. Yay, SRP is about people now! But it's a mistake ...
    How many people do you know? 100? 200? 1000? Imagine that each one is an "Actor" (whatever that is, exactly). Do you have 100, 200 or 1000 classes then, because that's sufficient for "being responsible to one actor"? Certainly not. A large app has tens of thousands classes. But why, if not for SRP reasons?
    Even Uncle Bob was not smart enough to find a good definition for more than 6 years - and still hasn't, IMHO. In contrast: by changing the definition, he brought more confusion and need for education.

    ReplyDelete
    Replies
    1. I think we agree more than it might seem.

      Let's say there is the V1 explanation and the V2 explanation. The first definition goes back to his 2003 book and says "a class should have only one reason to change". He frames it as a take on explaining cohesion ("the functional relatedness of the elements of a module"). Link is below. Its explanation is very simple a message of separation of concerns and cohesion, using various examples that if dimension x can/does change independently from dimension y and a class deals with them both, they ought to be split up. The examples he gives are reasonable and most people would agree on (e.g. separating business logic from persistence logic for an employee class).

      The issue I see is that people use it to an extreme and separate things so much that the class has lost cohesion. A silly example: an employee object calculates a current age and also calculates tenure at the company. Changes to that logic are clearly two reasons to change so clearly they must be proactively broken up into separate classes, right? I've seen people make arguments like this ad-absurdum to justify designs that have too many moving parts. And I think it's because you can naively interpret "a reason to change" at a tiny level of granularity.

      Years later, V2 of the principle comes along (which I am guessing is due to the misapplication of V1) which describes it as being related to people or areas of functional ownership. That's a very narrow definition (and subset of V1) that, while useful, isn't needed everyday. I suspect the second definition emerged because people were mis-applying the concept at small levels of granularity. Personally I see the V2 definition to be a natural consequence of DDD ideas and the V1 definition as the very definition of cohesion.

      p. 95 of book http://journals.mountaintopuniversity.edu.ng/Software%20Engineering/Agile%20Software%20Development,%20Principles,%20Patterns,%20and%20Practices%20(%20PDFDrive%20).pdf

      Delete
  5. For years, I always fail try defining what is "Single"-RP. Turns out a bias understanding. Much thanks for this enlightment post!

    Now I want to bring a practical example to be discussed. Given that I have a repository instance that fetch from a datasource and returns a User profile. Now let's say I have 10 owners that have EXACTLY the same use case.

    If I make those 10 owners depend on this repo, I clearly have violated the actual SRP principle (Yes?). But if I duplicate the same repo logic to satisfy the actual principle (one-to-one relationship), I think this is not being practical. A price to pay to maintain them and someone from workplace must refer me as a "dumb" and such a fanatic-being of a principle.

    What is your thought regarding this?

    ReplyDelete
  6. Like all good design question I think "it depends". A user is a horizontal concept across a system. Yes, 10 business owners might have a stake in it but I think it requires a "decider". In this case it might be a VP Eng/CTO/CIO type. What are the examples of how the same conceptual logic differs across the N business owners?

    ReplyDelete
    Replies
    1. Much thanks for the reply Joseph!
      So I'm actually a mobile apps engineer. In my marketplace app, I have a couple of module features. Let's say:
      - feature_about_me (to display user's summary profile)
      - feature_edit_profile (to display and edit user's profile)
      - ...

      All of these features need - well, the user's profile information. And I have a repository that can provide the profile data. Ofc I can make them depend on the repo to satisfy what they need (hence, they become "owners". Correct?). No problem.

      But with this post, I should have violated the actual SRP principle by turning the repo has more than 1 owners, right? So this example wraps-up my dilemma referred from my previous comment.

      Delete
    2. I could be missing something but I don't see anything problematic, from a design perspective, with what you're describing. One user object, one user repo would be fine.

      Delete
    3. 1. Oh so for overall, I should see an "owner" as an entity / group of members that share same interest rather than as a granular individual of instance?

      2. If so, let's refer back to my module features example. If at some point, feature_edit_profile requires to add some additional logic to the repo to satisfy what it needs, that's when I should separate it with different repo to satisfy its particular req. Correct me?

      Delete
  7. 1.) Yes. Best to think of it as an executive or functional group that will make a decision independently of another group. For example, the CFO (the leader of Finance department) may make decisions independently of the COO (the leader of the Operations group) and they might have their own business logic on a shared entity (e.g. employee). If that's the case you'd want to consider separating out the logic on an employee that could differ based on their different business rules.

    2.) I don't think your example merits separation unless I am missing something.

    My experience is that the need to separate things is discovered incrementally as the system evolves. Proactive separation based on a theoretical need usually isn't worth it because it's hard to predict the future. I think the best thing to do would be to read Uncle Bob's post carefully. I think you'll see that the SRP as he intends it is not something you need to think about everyday.

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

    ReplyDelete
    Replies
    1. "My experience is that the need to separate things is discovered incrementally as the system evolves. Proactive separation based on a theoretical need usually isn't worth it because it's hard to predict the future."

      ---

      This statement aligns my understanding! Again, much thanks for all your responses.

      Truly appreciate it and I will do my best to share this understanding to my workplace also back to community!

      Delete
  8. Thanks for the explanation. I have a graphical way to look at cohesion and separation, using a spreadsheet analogy. https://kenpugh.com/blog/the-spreadsheet-conundrum/ You always have multiple ways to do something and using a simple table without getting into the code can help in finding those multiple ways. As Jerry Weinberg said, "If you can't come up with three solutions to a problem, you don't understand the problem"

    ReplyDelete

Post a Comment