"Facts are stubborn things, but statistics are pliable.” ― Mark Twain
I remember (not too long ago) when I first heard of the Single Responsibility Principle. It’s a great thing for someone young in their programming career like myself. It’s a simple phrase to remember, meaning you’ve breezed through one-fifth of the “SOLID” principles, and it sounds noble, making it easy to trust without having to know much about its ins and outs.
For better or worse, however, I’ve become wary of the Single Responsibility Principle as I’ve read more, thought more, and make more mistakes about it. I think this wariness begins from a difficulty pinning down exactly what the SRP means. Take for example, the following quotes:
"The Single Responsibility Principle states that a class or module should have one, and only one, reason to change." ― Robert C. Martin, Clean Code
"A class should do the smallest possible useful thing; that is, it should have a single responsibility....SRP doesn't require that a class do only one very narrow thing or that it change for only a single nitpicky reason, instead SRP requires that a class be cohesive--that everything the class does be highly related to its purpose." ― Sandi Metz, Practical Object-Oriented Design in Ruby, Ed. 1
While not directly addressing the SRP, this other quote seems relevant to the matter as well:
"When we modularize a program, we are trying to separate the code into zones to maximize the interaction inside a zone and minimize interaction between zones." ― Martin Fowler & Kent Beck, Refactoring
I find it difficult to reconcile these three quotes when it comes to identifying how to determine whether a class has a single responsibility. This doesn’t make me feel good as I snoop around my code for violations of the SRP. And as I do this snooping, my wariness about the SRP grows thanks to a sense that defining a “responsibility” seems massagable, pliable, similar Mark Twain’s observation about statistics. For example, if my wife wants me to have the responsibility for “daily cleaning of the litter box,” to me that might sound like a single responsibility, so I’m happy. But she might view me as accountable for “vacuuming up bits of litter on the floor” as part of this overarching responsibility, so from her perspective, I have two responsibilities. Is my life in violation of the SRP? What if I delegate the sub-responsibility of vacuuming to our child (successfully)? Am I back in the good graces of the SRP? Or have I just convinced myself of that?
When working on Tic Tac Toe in Ruby recently, I similarly found myself merrily labeling classes as having a single responsibility. “Why, the Game class coordinates between the player classes and rules classes to execute the game! One responsibility!” But subsequent feedback and suggestions pointed out otherwise: the class did too much, being responsible for (at various overlapping points) object instantiation, game looping, printing to the console, reading input. These things should be broken out into separate classes that are, in turn, responsible for them.
Once these points were made to me, they made sense. I could see how making changes along these lines improved the code and made it more adaptable to any future changes. But where exactly did I go wrong? On one level, I’m not sure, because I find myself good at looking at a given class and talking my way into a single responsibility for it. A knack for “spin” remaining from my lawyer days, it seems….
But in any event, given the different definitions above for the SRP, and given the malleability of declaring a responsibility itself, my hunch is that examining code for violations of the SRP is an approach similar to solving a Rubik’s cube: you’ve got take what you’re examining and look at it from different angles, asking yourself different questions as you go. For example, these questions could be general ones, like these:
What is the likelihood that part of your code could change for some reason? Are there many such reasons it could change, including due to dependencies or assumptions somewhere? If so, perhaps the code should be broken down into separate classes to isolate dependencies and/or isolate other areas from having to change. (Based on Robert Martin’s quote above.)
Is everything in chunk of code cohesive – i.e., is it all related to accomplishing a unified purpose? If there are multiple unrelated “purposes” that you can discern, or if there is there a sub-purpose here that other classes may want to take advantage of, perhaps this chunk of code should be broken down into separate classes. (Based on Sandi Metz’s quote above.)
Is one particular class constantly sending messages to another particular class? Perhaps there is a violation of the SRP because a responsibility has been split among two classes when they should really be one unified class. (Based on Martin Fowler & Kent Beck’s quote above.)
General questions like these may help, but I smell on the horizon that in practice, programmers tend to agree that certain specific tasks should be isolated within their own classes. For example, based on the feedback I’ve received:
Does your program require instantiating objects (which is highly likely)? Let your program logic rely on abstractions of these objects. Then have a class (or classes) with the responsibility for instantiating these objects, and inject the objects into the class controlling your logic. This way, you increase the likelihood that your program logic can manipulate other types of objects (perhaps through polymorphism), not just the objects you contemplate now.
Does your program require input from and output to a user (again, which is highly likely)? Let your program logic rely on abstractions of the input and output, while isolating the implementations of a particular input and output streams in separate classes responsible for that implementation. This way, if your specific input or output streams change, your logic doesn’t have to. (In my mind, this is similar to the logic behind the separation of concerns inherent in the “MVC” model.)
Does your program rely on an API controlled by someone else? A change in that underlying API could blow up your program if your logic makes direct calls to that API. To minimize the risk of this, create a “wrapper” class that is responsible for speaking to the API on behalf of your program’s logic. Your logic can call methods defined in your wrapper class, and if the underlying API changes, your wrapper is the only thing that has to change to accommodate this development. Your program logic doesn’t have to.
Just as you can break a story down into a beginning, middle, and end, can you break your program logic down into different sections, and does it make sense to have different classes control particular sections? By isolating sections this way, you minimize the chance that a change in one section will affect other sections.
These questions are more specific than the preceding ones. Are these specific questions on the right track? Well, even from a light flip through of the Gang of Four’s Design Patterns, one can surmise that professional programmers tend to agree that, if the complexity is worth is, certain specific responsibilities should be sequestered to a separate part of your program. These responsibilities include those above that I learned about: instantiation of objects (“Abstract Factory” and “Builder” patterns), translating between input and output or an external API (“Interpreter” pattern), separating chunks of logic from each other (“Strategy” and “Template Method” patterns), and many more.
At the end of the day, my burgeoning and evolving sense of the SRP is telling me that the SRP takes the shape of both generalized good advice to keep in mind and generally accepted practices that professionals recognize as valuable. And the linchpin connecting these two manifestations and making them easier to understand is experience playing with and wrestling with them both. That comes with time, which means that any wariness about the SRP will likely ease up as I learn more and grow more.
Never one to miss an opportunity to lump everything together with a spin, though, I’d take a shot and say that if I had to summarize everything above, it’s that the SRP wants us to ask:
- Are there portions of code in your program that should be put together in their own class either because it logically makes sense to do so (i.e., these portions of the code depend on each other or are conceptually related) or because there is a likelihood that these portions of the code might change or be substituted, so you want to isolate them from the rest of your program?
If so, then put those portions of code in their own class.
Will this summary hold up? Time will tell!