The Single Responsibility Principle (SRP) and Don’t Repeat Yourself (DRY) are two common principles of good software engineering. This article is probably a statement of the bleeding obvious, but I was struck by how the two principles could be illustrated by simple variations on one simple diagram. I couldn’t remember seeing such diagrams before and in case, like me, you often understand pictures better than you understand words, I thought I’d do an article about them.
The diagrams that follow aren’t complex or scary, but I’ll still introduce what they contain because the contents are deliberately defined in vague and hand-waving terms.
The diagrams will contain two sets of things:
- Bits of code
- Details of how to solve a problem
Bits of code are methods, classes, name spaces and so on. I’m imagining that the diagram can be zoomed in or out, so that at its most zoomed-in the bits of code will all be methods, then when you zoom out a little they will all be classes etc.
Details of how to solve a problem are the ways you have decided to break up the overall problem to solve it a bit at a time. I.e. there’s one overall problem, whose solution is expressed in terms of smaller problems, which are in turn broken down etc. The problem at the top level might be let the user send and receive email. After this is repeatedly broken down into smaller and smaller parts, one part of this might be the problem make an HTTP call, handling success or failure. It’s important to keep separate the two concepts of a problem and its solution. DRY doesn’t worry too much if your code makes lots of calls to method X that handles the details of HTTP calls (which is lots of instances of other code asking X to solve the problem of making an HTTP call). It does worry if you have lots of instances of all the details of how to make the HTTP call (which is lots of instances of the solution).
Unlike bits of code, there will be lots of different levels of abstraction at once in terms of the problem being solved. There will be a method that is the entry point to your code, and this could be said to be solving the whole problem. There will also be methods that just make an HTTP call, or respond to a user clicking on a particular button in the GUI.
It’s good practice to limit the number of abstraction levels present in each method. Ideally there should be two: the problem and its solution in terms of problems one level less abstract. While it’s true that a problem and its sub-problems are related (which could be thought of as a reason to include all the solutions in one method), they are still different problems. The code that solves the problem let the user send and receive email shouldn’t care about the details of the problem make an HTTP call.
As I mentioned before, if you violate DRY then the details of one problem’s solution are in more than one place i.e. more than one bit of code. The diagram for such repetition is:
The details of how to solve problem Y are in bits of code A, B and C. The details should be in only one place e.g. A, and then all other places that need the problem solved (e.g. B and C) should call A. I’ve deliberately left X and Z out for clarity.
Violating SRP means creating something like a Swiss Army Knife or God class (or method). It does more than one thing, i.e. it solves more than one problem. Note that it’s fine if a class solves one problem and all its sub-problems. That is, the class solves problem A, and also the solution to sub- or child problem B, which in turn has child problem C (which is a grandchild of problem A). For instance, a public method of the class solves problem A, and the public method calls private methods that solve sub-problem B etc. The violation would happen if a class or method solves problems A and B where A and B aren’t in ancestor / descendant relationship but are e.g. siblings or cousins.
The diagram for a Swiss Army Knife class or method is:
Bit of code B has the details of the solution to problems X, Y and Z. It should have the details of only one problem’s solution e.g. X, and then delegate the solution of other problems (Y and Z) to other bits of code that it calls e.g. A and C.
It can be tempting to think of SRP as only a force that pushes you towards smaller and smaller classes, methods etc, which means that you have more and more of them. While the Swiss Army Knife is usually not the best approach, the original definition of SRP shows that it’s also possible to break things up too much. Things with the same business reason to change should go together.
You can probably tell what the diagram for good practice will look like, and I include it here for completeness.
Each bit of code has the details of one problem’s solution, and each problem is solved in one place.
One way to think of satisfying both SRP and DRY is to have a one-to-one relationship between bits of your code on the one hand, and the details of solving the problems that are the reason for the code’s existence on the other. One-to-many and many-to-one (or even many-to-many!) relationships mean that code is getting tangled, unclear and hard to change.