4

Can you in "plain English" explain why we need Single Responsibility Principle?

Especially that it moves the what I call "bloat" into other places. See below for more info. By bloat I mean in my specific case, I have multiple responsiblities in a Controller that exhibit themselves as functions/methods. By applying SRP in the way that I understand, breaks up the Controller into several Controllers, each with one method. The "bloat" is therefore moved from "number of methods" into "number of files".

The article in the wiki link above explains what SRP is, but it does not seem to tell me "why" we use it. I'd like a not-so-technical (or maybe even a technical) reason/explanation/sense as to why we need this principle.

Because in my current experience, implementing SRP, leads to smaller, more narrowly-aligned code, but it creates more files.

Example

I have a controller file that can have many various actions/methods inside of it. To keep all those actions in the controller, I have to bloat the number of dependencies I have to pass to the controller, because dependencies must cover all possible actions, even if any one particular action does not need all of the dependencies.

so I can break up the controller into 2 or more pieces to where I have one action per controller. This satisfies the SRP, but it bloats the number of files I have to create.

Example

/*
 * Repository initialized with $quoteId
 */
class Repository
{
    private $quoteId;

    function __construct($quoteId)
    {
        $this->quoteId = $quoteId;
    }        
}

/*
 * Controller initialized with $repository
 */   
class Controller
{

    private $repository;

    function __construct($repository)
    {
        $this->repository = $repository;
    }        

    function addForm()
    {
         //repository is initialized elsewhere with $quoteId
         $this->repository->getFormDataFromQuote();
    };

    function viewForm()
    {
         $id = int_val($_GET['id']);

         //does *not* use $quoteID
         $this->repository->getViewDataFromId($id);
    };
}

SRP

To abide SRP we can break up Controller into two, one for addForm, one for viewForm. We then can break up the Repository into two as well, one for each method in Controller. Thus, we started with 2 files, we will end up with 4 files.

Drawbacks

I interpret SRP here as "Break up the controller" (and, I presume any further supporting files as well, such as Repository here) into two in this case. Thus in the above example, there will be ControllerAddForm and ControllerViewForm, and if I am using a repository for those methods, which I am, I have to create RepositoryViewForm, and RepositoryAddForm to satisfy SRP as well, because different repository methods are required for the different actions of the Controller. Thus, I get 2x file bloat. Why again is SRP recommended despite moving the bloat into the number of files instead of into the number of methods per file.

Dennis
  • 8,157
  • 5
  • 36
  • 68
  • 3
    It sounds like you are confusing functions performed with responsibilities. Something can have a single responsibility, such as managing a user-entered form, but it will perform many actions. Persisting the form to a backing store would constitute another responsibility that is not (directly) part of your `Controller`, since the encoding and saving/restoring responsibilities are different. – BobDalgleish Nov 07 '17 at 21:59
  • I am not sure I totally understood. But in my case, responsibility of "*adding a new form to the database*" needs to know `$quoteId` (to prefill form with Quote data), while responsibility of "*show data from a form, given that data's ID via URL*" does not need to know `$quoteId` (but we still must use it for Repository to work). Would you say instead that these are the same responsibility that just happened to reside in two functions? – Dennis Nov 07 '17 at 22:02
  • 1
    "Responsibility" is a vague term but a single responsibility in higher level classes can mean multiple responsibilities from a lower level perspective. – Frank Hileman Nov 07 '17 at 22:08
  • 4
    SRP is recommended because people usually combine it with common sense. In a hotel, you have a chef whose single responsibility it is to cook all the foods required. You don't have one chef responsible for fried eggs, one responsible for boiled eggs, one responsible for scrambled eggs, and a few hundred others. – gnasher729 Nov 07 '17 at 22:08
  • 1
    Or responsible just for the eggs, for that matter. – Robert Harvey Nov 07 '17 at 22:10
  • 2
    @gnasher729: there is no such thing as common sense. – whatsisname Nov 07 '17 at 22:23
  • alright. I am getting an impression that in my case, my SRP is "any action related to managing CRUD for this particular form", so I do not need to break up my classes, nor repositories. I do however need to alter my implementation details to where absence of `$quoteId` does not break my implementation of `Controller::viewForm()`. Because currently, calling this method when `$quoteId` is not available, breaks my `Repository` initialization. One way to avoid this was to break up the classes, but that not being necessary, a different implementation is required. – Dennis Nov 07 '17 at 22:34
  • this is a CRUD application here, and I could argue that to Create a record is a different responsibility from viewing (Reading) the record, and same goes for Deleting and Updating it. So even though all four CRUD operations deal with the same set of form data, responsibilities for each are different, or at least can be said to be defined to be different. But who is the authority to say how to define them? One thing is, I should not define SRP based on commonality of dependencies (which I was trying to do here), but on the purpose behind the task. But do you define CRUD as one or four SRPs? – Dennis Nov 07 '17 at 23:08
  • By number of files, do you mean code files? Having on big file is somehow less "bloated" then the same amount of code in smaller files? – JeffO Nov 08 '17 at 14:53
  • yes. I mean code files in this case. Each class gets one file, so more classes => more files. More files means more cognitive load. For example, having one file that deals with everything related to "Sales Order" CRUD is clear. Having 4+ files dealing with "Sales Order" you have to spend more time understanding how they connect together. More files also means more I/O requests, so a bit more load on the file system. – Dennis Nov 08 '17 at 15:04
  • Since I've already written a quite abstract comment about SRP being derived from high cohesion, I can elaborate a bit on it. I think one of the manifestations of high cohesion is that the abstraction level of class name (in other words, its *implied* responsibilities) coincides with abstraction level of its real behavior, expressed with its methods. I gave more broad answer here: https://softwareengineering.stackexchange.com/a/360262/33850 – Vadim Samokhin Nov 11 '17 at 08:57

4 Answers4

12

You need SRP for the same reasons you don't let your Sous Chef fix your car. They have different responsibilities.

Each person specializes in a different area of expertise. If it helps, think of your classes as employees. Give each one a job title; that's usually the name of each class. Work out the "area of expertise" of that class, and then write code that fulfills that area of expertise.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
  • 3
    I like this way of thinking of it. Mostly because it means when I'm thinking about the CarRepair class I won't be thinking about how much salt to add. – candied_orange Nov 07 '17 at 23:24
  • 2
    @CandiedOrange Table salt is naturally abrasive and absorbs some grease, making it a surprisingly reasonable choice when doing car repair. – Joel Harmon Nov 08 '17 at 23:31
  • @CandiedOrange: you must not know many mechanics. They're full of salt! – Bryan Boettcher Nov 09 '17 at 15:19
  • The metaphor is dangerous a bit. You might let your Sous Chef fix your car if he fixed cars in his spare time and was good at it. The reality is that specialization (managing responsibilities) takes cognitive effort both with people and with software classes. If someone came up with a company that fixed cars and served gourmet meals to customers while they waited, this kind of design could work. I think there's more to SRP than this... – Fuhrmanator Nov 10 '17 at 19:45
  • @Fuhrmanator: In software, generally your Sous Chef doesn't fix your car in its spare time. That's kind of the whole point here. If you genuinely needed a Sous Chef that Fixes Cars in His Spare Time, you would undoubtedly create a class that inherits from SousChef, and you'd still be squarely in SRP territory. So no, there's no magic here, and yes, it is just a metaphor. – Robert Harvey Nov 10 '17 at 20:16
  • I understood the OPs question as being at which point does one decide what is a separate responsibility. I once went to a sporting goods store and asked a sales person advice about grips for a tennis racket. Her answer was that it wasn't her department (she was in baseball). That's SRP under the influence of capitalistic forces (it probably made training employees cheaper?). This example shows that it depends on the application and how beneficial it is to specialize. – Fuhrmanator Nov 10 '17 at 22:04
  • @Fuhrmanator: That's why it's just a metaphor. – Robert Harvey Nov 10 '17 at 23:52
  • While a chef and an auto mechanic might be completely different responsibilities that don't complement each other there are other examples where 2 responsibilities do complement each other and make things convenient. A car mechanic that also runs a car rental business could be 2 responsibilities that go well together. He is responsible for fixing your car and for renting you a car while it is being fixed. The same goes for code, dividing up things into single responsibilities can make things extremely inconvenient to whoever reads the code next – morbidhawk Nov 22 '17 at 16:35
  • @morbidhawk: By your own admission, those are *two different businesses.* – Robert Harvey Nov 22 '17 at 16:40
  • I was countering your argument "you don't let your Sous Chef fix your car" with you do let your auto mechanic rent you out a car, since it's convenient. While I daftly misrepresented my real argument with that statement the real counter argument that it can be beneficial to have a single person or business fill two roles still applies. – morbidhawk Nov 22 '17 at 16:49
  • @morbidhawk: Which is why it's just a metaphor. I've gotten this kind of pushback twice now; do I need to further clarify my answer? The OP's question was "Why do we need SRP?" This is why. That doesn't mean SRP is not misused. *It is,* often. – Robert Harvey Nov 22 '17 at 16:55
  • Perhaps, I don't think any of the answers here are convincing answers to the question "why we need SRP in Plain English despite it's drawbacks?" In my experience the code that I write that doesn't follow SRP has been the most readable and maintainable code I've worked with. The level of indirection SRP creates comes at a readability cost. – morbidhawk Nov 22 '17 at 17:00
  • How would you respond to this issue of code execution path being difficult to read when broken apart? There is even research that shows readability suffers when code is not inlined – morbidhawk Nov 22 '17 at 17:07
  • @morbidhawk: I'm pretty sure that goes beyond "plain English." SRP is a reminder that your class might be doing too much, and that's all that it is. But in daily life, responsibilities are carefully delegated and compartmentalized; why should it be any different for software? – Robert Harvey Nov 22 '17 at 17:11
  • I understand your argument aligns with OOP philosophies, i just haven't found the philosophy of breaking things apart to be convincing, I'm trying hard to understand it I really am. Why would I break apart a recipe that is explained one step after another into sub-recipes. I would jump into modifying a sub-recipe without fully understanding what came before, what temperature is the pan, where is it being cooked, what spices were used already? A wrong assumption can destroy the integrity of how it turns out. – morbidhawk Nov 22 '17 at 17:33
  • `Why would I break apart a recipe that is explained one step after another into sub-recipes.` -- You wouldn't, unless **it really is a sub-recipe.** If a recipe for cake calls for homemade caramel frosting, that caramel can be detailed in a separate recipe, and *it doesn't negatively impact the main recipe.* In fact, it benefits the main recipe because the caramel can be made separately. – Robert Harvey Nov 22 '17 at 18:02
12

SRP is one of the most misunderstood software engineering principles. There is not even a precise definition of what a "responsibility" is, making this more difficult to understand.

Roughly speaking, a "responsibility" is not a single atomic action, but rather, a set of actions that are closely related. Adding a form, viewing a form, submitting a form, etc. are all the same responsibility of "managing form X."

To abide SRP we can break up Controller into two, one for addForm, one for viewForm.

No. To abide by SRP you have separate controllers for managing quotes and managing customers. They may delegate to models that manage persistence for each data type (quotes and customers).

In general, refactoring large units of code into smaller units of code is a good idea up until the point that there are too many small units of code to reason about. Personally, I think that keeping all of my "manage quote form" code in one class is a great idea: I know exactly where to look for controller code for quotes. I know all of the code is there for that purpose, but no code for managing customers.

Also note that SRP as it applies to a web-based quote management system will be different than with the Linux kernel. Take a step back and look at the overall design, and try to find the natural divisions in what the code is doing. Then ask one of your peers.

  • 4
    Frankly, most of the SOLID principles are so vague as to be useless unless you already understand the design principles that motivate them. – Derek Elkins left SE Nov 08 '17 at 09:12
  • Thanks. I still don't understand it, so I'm trying to get more clarity on SO (https://stackoverflow.com/questions/47169129/what-are-the-srps-of-a-simple-typical-blog-like-crud-application) – Dennis Nov 08 '17 at 15:17
  • @Dennis your other question was closed as too broad, which I would expect. I am not sure stack exchange is the right place for such an open-ended question. Perhaps [/r/learnprogramming](https://www.reddit.com/r/learnprogramming/) might be able to help? I searched there and found [this post](https://www.reddit.com/r/learnprogramming/comments/4p526b/single_responsibilty_principle_question/) which at first glance seems to contain some good advice. –  Nov 09 '17 at 01:01
  • 4
    SRP seems to be extreme in that regard. Refactoring into smaller units because one unit does too much is generally fine. Refactoring into smaller units because of SRP is usually motivated by incorrect understanding of SRP. – gnasher729 Nov 10 '17 at 21:56
3

SRP is dangerous (as are most design principles) if you follow it as dogma. My perspective is to respect SOLID but beware of its strong cult(ure), especially when someone's shedding more heat than light with citing those principles.

When I read your question, I thought of SRP as being in opposition to YAGNI.

Using your controller example, it makes little sense to separate your controller out into distinct classes if the requirements never change, you won't be maintaining the project after some "Shark tank" demo, and/or you'll never need to reuse part of your controller logic in another project. They're all big ifs maybe.

Robert Martin gives a detailed example using code in a bowling project. The design leads to a class Game that has two responsibilities: keeping track of frames, and calculating the score.

enter image description here

In the pair-programming design experiment in his book, one of the developers ends up suggesting to refactor these separate responsibilities into distinct classes, Game and Scorer, citing SRP as the motivation. A sarcastic comment is made that Linux programmers like to do everything in one single unreadable function...

It's a good example of the Ying and Yang of the SRP. Having separate modules for the Game and Scoring responsibilities has an advantage of allowing the two to evolve more independently, perhaps even allowing a new programmer to find where in the code that tricky bug in scoring might be (there are fewer lines in Scorer than if all the code were combined in one module), etc. These classes could theoretically be reused in other applications that need their services (Jarts anyone?).

But there's a down side: anticipating changes that won't ever occur is a waste of effort. What changes could possibly come to the rules of the game of bowling!? Theoretically they exist (hey, there could be an executive order from 45?) and if they happened, having separate modules would surely be better then, right?

Alas, YAGNI says don't waste your energy on changes until they come. It's over-engineering.

Finally, SRP (and responsibility-driven design in general) is a heuristic (meaning it seems to have worked in the past, but there's no guarantee that by applying it you will benefit). Nobody's done enough research to tell you when it pays off. Design problems are hard, and trying to use a "principle" dogmatically will lead to disappointment.

I found the Principles Wiki really interesting (albeit incomplete) because it attempts to map out the relationships between these various design principles. For SRP, there are many related principles, e.g., separation of concerns, Curly's law, etc. Reading those will also help you understand the trade-offs.

Fuhrmanator
  • 1,435
  • 9
  • 19
  • 2
    `I thought of SRP as being in opposition to YAGNI`. Also in opposition to KISS and non-clever programming. By not subdividing the code into cleverly named subproblems the code will be more readable from beginning to finish and won't cleverly hide away what's happening. – morbidhawk Nov 21 '17 at 22:22
1

The Single Responsibility Principle (SRP)

Classes should not have more than one focus of responsibility. Business context will also drive the level of segregation of responsibility in a class.

Note:

  • Classes can reasonably be involved in different interactions, it is the focus that is the issue.
  • This principle is almost identical to the cohesion principle

SRP Analysis: For example, look at each method, x, in the class, say Car, and ask, "does Car have primary responsibility for x-ing?" If the answer is no, the method may not belong there. Ask with regard to the object, not the action.

kerrin
  • 290
  • 2
  • 9
  • 3
    I even think that SRP principle is on top of the concept of cohesion. All the SOLID can be derived from these two: strong cohesion and low coupling. – Vadim Samokhin Nov 08 '17 at 07:33
  • Martin gives yet an even subtler example of a `Rectangle.draw()` and `Rectangle.area()`. The class seems to be OK until you consider how the `GUIApp` might require a change that is incompatible with the `ComputationalGeometryApp` (or vice-versa). The SRP is applied to create a new class `GeoRectangle` taking the `area()` method from the original `Rectangle`. Although the example makes sense, it doesn't convince me what kind of *change* would break the original design. It's what I'd call more "heat" than "light" when the example's not concrete enough. – Fuhrmanator Nov 10 '17 at 22:17