1. 5
  1. 3

    I believe SRP to be an anti-principle when applied to object-orientation. It’s sole use in this context, is as a definition that can be repeated parrot fashion in a job interview. On the other hand, it can be applicable to a sub-routine, function or method.

    1. 2

      I have a much much better explanation.

      Ignore all this hand waving warm fuzzy waffly “Single” and “Responsibility” or “Requirement” shit.

      Stick to the Stroustrup Principle.

      Bjarne Stroustrup: My rule of thumb is that you should have a real class with an interface and a hidden representation if and only if you can consider an invariant for the class.

      https://www.artima.com/articles/the-c-style-sweet-spot

      Now write down an expression to evaluate whether, for this instance, that invariant holds.

      Is it of the form… exp1( a1, a2, …ai) && exp2( ai+1, ai+2, …ai+n) …where ai for i = 1..n are instances variables?

      Then clearly you could decompose this object into an object containing two objects one with invariant exp1( a1, a2, …ai) and the other with invariant exp2( ai+1, ai+2, …ai+n)

      Duh! Obvious go and do that. Your code is simpler, more reusable, more testable.

      If you can write the invariant in the form….

      exp1( a1, a2, ...ai) || exp2( ai+1, ai+2, ...ai+n)
      

      You’re obviously have some pretty confused shit going on… don’t do that, break it into two independent objects with invariants exp1( a1, a2, …ai) and exp2( ai+1, ai+2, …ai+n) respectively. and then think damn hard about what you’re actually mean because I think you might be missing something.

      And ps: The LSP is not a warm fuzzy guideline, if you violate LSP you have a bug. Simple as that. Violate LSP you have a bug, you might not be able to reproduce it from your UI right now, but it will mysteriously and randomly leap out and bite you if you change your program or reuse your code.

      ps: LSP is really all about invariants.

      ie. Ignore SRP, look at your invariant… are the simpler, more compelling classes visible by decomposing the large class into simpler classes with simpler invariants. Can you build up a complex invariant from simpler more compelling classes?

      1. 1

        Your rule of thumb is strongly biased towards classes (because instance variables). But the article also mentions modules, which are more interesting topic. How would you handle those?

        1. 2

          Depends critically on what you mean by a “module”. It’s one of the most overused and under defined terms in the industry.

          In the C++ world they have only been formally defined in C++20

          If you’re using “warm fuzzy whatever you want them to mean” words…. expect “warm fuzzy whatever I want them to mean” advice….

          So what I mean by module is “collection of functions and variables, some of which are publically visible outside the module, some are not”. Whether this is a file or a directory or a library or a collection of submodules…. remember we’re warm and fuzzy here so I don’t care. (Hint: By “visible” I mean the build will break if if something in another module attempts to reference or use a non public symbol. I don’t care about any other definition. You can wave your hands all you like, but if the build doesn’t break, your definition means nothing.)

          The core ideas then are…

          • Encapsulate state.
          • Reduce scope (reduce the number of things that are visible at the outer most scope).
          • Remove all cyclic dependencies.

          Prefer “non member, non friend” functions to methods to improve encapsulation, and then Ban cyclic dependencies between modules, and then focus on Reducing Coupling and Enhancing Cohesion.

          Between module coupling is bad and should be refactored where appropriate to weaken it. Especially the various horrid connascent flavours of coupling https://connascence.io/ i

          Within module cohesion is Good, and any function or class that is not cohesive with the rest of the module should be pulled out of the module into it’s own.

          Tools for doing this are the “I” and the “D” in SOLID.

          I really don’t care about domain level “requirements” in module layout. I care about reducing the number of things I need to know and worry about before making a beneficial change.

          In fact that is the only design criteria when operating at large scale. “Reducing the number of things I need to know and worry about before making a beneficial change.”

          1. 1

            Thanks for the link, I’ll take a look.

            I’m not sure why you insist on defining modules through “C++20”, the original article discusses SRP which is supposed to be relevant (almost) anywhere. I would say that the idea of a “module” is as defined as the idea of “SRP” for purposes of that discussion.

            1. 1

              That the problem… the article doesn’t define what a module is beyond the somewhat recursive notion of “it’s a single requirement”.

              Ruby’s modules are different from C++20’s modules and C doesn’t have modules… I bet half the confusion and debate would go away if people actually knew what the other guy meant by “Module”.

      2. 2

        What about dependent requirements?

        1. 1

          I’m not sure if I understand the question. I guess you refer to this sentence from the article:

          The single responsibility principle states that a class should at most be responsible for implementing a single independent requirement.

          I think about requirements through the lens of concatenability principle: https://minimalmodeling.substack.com/p/concatenability-principle. If we have a “liked tweets” and “bookmarked tweets” features than the “tweet page” depends on both of them, and I guess it makes them dependent?

          But I think that there is nothing wrong with such “umbrella” requirements. The most important umbrella requirement is the website front page, for example.