Every once in a while one comes across a concept that he always knew was right but did not know how to apply. For me the concept of writing methods/functions having one(and only one) purpose was one such thing.
What is Single Responsibility Principle (SRP)?Functions should do one thing. They should do it well. They should do it only. - is how Robert Martin describes this concept in his book Clean Code1(which I would highly recommend). The issue with this has been to understand: what is one thing? Methods are written to do one operation after all so how do you determine the "one thing" that needs to go into the method. A quick review of any long method is typically enough to figure out what parts of code can be extracted into another method. How much to extract and where to stop is something that I've struggled with till recently. Let me try to explain these concepts using some material from Clean Code and some of my experiments.
How to determine whether a method is having single responsibility?Sometimes the name of the method itself reveals that there are multiple responsibilities with the method. Does the method reads like an algorithm is what I like to check. Preferably like an algorithm that is simple to understand in one reading for someone who does not understand too much of programming.
Robert Martin in his book Clean Code describes this as "In order to make sure our functions are doing “one thing,” we need to make sure that the statements within our function are all at the same level of abstraction." Now what does this mean?
Basically it means that if your method's responsibility is to make a cup of coffee, it should just make coffee. It shouldn't be concerned with the act of visiting the grocery store to buy the Nescafe, sugar, etc. These related activities can be abstracted to respective methods/objects. This helps focus on the act of making the coffee right, irrespective of what happened while buying sugar. The same concept in turn would apply to the act of buying groceries and so on.
Here's an example of re-factoring code to have a single level of abstraction.
The above code tries to remove a certain quantity of an item from a Shopping Cart by iterating through an internal data structure. It is pretty clear from this code that the level of detail spans at least 3 levels of abstraction from the intent expressed by the method name.
Here's the same method after a first pass of refactoring.
We can see from the code above that the task of finding the reference of the item in the Shopping cart has been abstracted to another method. There is still however some code that decides how to remove the item from the cart that can further be exracted.
This version of the code reads like an algorithm which goes:
- Get a hold of the item that needs to be removed.
- Remove it appropriately.
As can be seen, the extracted methods themselves go one level of abstraction below the intent stated by it's name. Also a noticeable side effect is that this led to the discovery of the ShoppingBasketItem object.
How much to extract?You will know when to stop extracting when the code in an extracted method almost exactly matches the name of the method.
Benefit of SRP.One obvious benefit of SRP is enhanced readability. Other benefits of SRP is that one may discover new Objects while extracting code into Methods.
How to refactor?Martin Fowler in his book Refactoring stresses on the point of having a solid set of unit tests before refactoring code.
A typical refactoring should include:
- Create a set of test cases to test the functionality of the method being refactored.2
- Iterate through refactoring and testing (using unit tests) till you reach the required level of abstraction for each method.
ConclusionSingle Responsibility Principle is a great guideline to make code readable. It will lead to an increase in the number of methods in the class or number of classes. The code will however express a clear and crisp flow of logic within and among the units.
Excerpts from Clean Code - Robert Martin and Refactoring - Martin Fowler. Code snippets and example are my own.
1. A highly recommended book.
2. The above refactoring exercise was backed by a set of unit tests which validated the functionality of the method.