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,267
  • 6
  • 38
  • 70

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
  • 200,592
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.

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,475
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