For any of you who know who Uncle Bob is, you probably know he is a “fanatic” about the idea that methods should do one and only one thing. If you have any doubts about this, check out his recent blog on the matter. However I put “fanatic” in quotes because I am in complete agreement with him on this (and most other) points.
There are lots of reasons for keeping your functions this tight. Combined with good method names, it makes for great self-documenting code. It makes code reuse a lot easier because you never have code that does one thing tangled up with code that does something else. That in turn makes it easier to keep your code DRY; if code is easily reused, there is rarely any reason to duplicate it. All of these points, and their relationships, are often discussed.
One relationship I have not seen mention of, though it is implied in the above, is the relationship between Do-One-Thing (DOT) and the Open Closed Principle (OCP). This relationship has had opportunity recently to re-impress itself upon my mind, so I thought I would take some time to discuss it.
For those not familiar with OCP, the Wikipedia entry gives a concise explanation. For a more in depth, applied look, see Uncle Bob’s treatment of it. For the real short summary, here is the definition from the latter.
Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
Another way to say this which gives the sense in which I wish to discuss it here is this: Given an existing class, if I need to modify the behavior of the class, I should be able to do so solely by means of subclassing it. I should not need to make any modifications to the class itself.
To clarify, this is important in scenarios where the original class is used by code beyond that which needs the modification. If all code that uses a class needs the modification (such might be the case with a bug fix) then by all means the original class should be modified. But if a modification is needed by only some users of the class, then OCP comes into play. In this case we want to leave the original class unmodified in order to ensure that the other users are not affected by the change. The code which needs the change, and only that code, uses the new subclass.
In contrasts to OCP, what often happens is this: I have a new feature to implement that requires a slightly different behavior from the FooBar class. FooBar is used by other features of the application that rely on the current behavior. I could just make a copy of FooBar and modify it to fit my needs, but DRY says that is bad, so I correctly decide to subclass FooBar and just override the method that needs to behave differently. So far so good.
So I pull up the source code for the FooBar class and find the code that I need to override. It is in the Transmorgify() method. But now things get messy. Transmorgify() doesn’t adhere to the Do-One-Thing principle. It does several things. But I only need to override one of them. Now I am in a conundrum. I have two options.
- I can override the current Transmorgify() method and copy its current implementation into my override, and modify the part that I need modified. That violates DRY.
- I can refactor Transmorgify() by extracting each behavior into its own function as Uncle Bob demonstrated above–then override the new more narrowly focused method. But that means modifying the existing code and possibly breaking something–a potentially costly error.
As can be seen, the failure to adhere to Do-One-Thing resulted in a failure to adhere to the Open Closed Principle. To put it succinctly, a class cannot be closed to modification if any behavior that might, in the future, need to vary independently of another behavior is implemented in the same method as that behavior.
That is an ideal statement. Realistically, it may be hard to identify every potential vector for change let alone isolate them. And even if it can be done, the ROI might not be there. Uncle Bob himself acknowledges this in his treatment of OCP. (See page 6 on “Strategic Closure.”) But this really goes back to the question of how far to take Do-One-Thing. If you can, “do it till you drop.” But if time constraints and ROI issues impose, then at least go as far as you can and error on the side of excess. Why? Because we are human and we tend to error the other way because it is easier and faster. So trying to error on the side of excess is more likely to get us into an appropriate middle ground.
No matter how hard we try to avoid them, ultimately, we are going to encounter scenarios like the above. When it happens, the best thing is to have a comprehensive set of tests in place and refactor the original class as needed.