A few days ago a fellow reader of the XP list posted this problem : he was test driving some functionality and he found that he kept on changing his own tests. This striked him as a potential issue, as it could be an infringement of the Open Close Principle.
This brought the focus on the role of OCP for detection of the need for refactoring in iterative development.
As a result to my response to that post I was later asked where one could read more of my thoughts regarding the use of OCP as a guide for identifying code smells.
Since I usually write this blog in Italian and since most of my thoughts on this topic are bound to slides of past seminars, I’ve devolved a few hours since yesterday to coalesce my thoughts from minnenratta and elsewhere regarding the guiding role of principles while evolving the design of code.
In the post I said that the third time I happen to change the code in a base class I will worry why I am changing it and that noticing the opposite, that one is changing less and less code in order to obtain a desired behavior, is a hint that OCP is being followed.
I think that OCP, like other design principles should never be used up-front in evolutionary development, but only as a tool for improving a design that has already proved a limit and thus, is leading to degradation. To prove that a design is limited your code should show degradation.
For instance I believe that the Single Responsibility Principle should be thought of as Single Variation Axis Principle instead , as a Responsibility is associated and identified with a type’s interface method. This leads to applying it to any disjunct logic of a class before any bell has rung and no need for a further axis of variation has appeared.
To tip the hat to fractals I could restate this to show the simmetry with the main iteration that led me to creating degradation in the first place : Code degradation is the red bar of my design. First I need the red bar then I implement the design that solves it.
Back to the matter of base class modification. I am not sure that it is OCP that highlights it in my mind as a degradation. At that point I’m still realizing that something is indeed a smell. OCP does come into play later on, when the hint transforms in an identified smell and I am trying to understand what caused it and thus what I should do to clean it (and thus pick the right refactoring moves).
I would define a “hint” as something odd that I notice by difference from experience and training, something which might be a smell.
“Oh… why am I touching this again?”.
Why am I asking myself that question? Right now I would bet it’s not because of my knowledge of OCP, it should never be.
This is very likely the source of the original poster paradox with OCP. He was both envisioning up-front design by strategically closing his code, and he was using OO principles such as OCP or SRP as first-level hints. In that context they are far too strong and often misleading.
That said, I believe that the sensibility to changing code in base classes should rather spring out from a broader, weaker heuristic : “good base classes should have few reasons for change and many reasons not to change”. This heuristic is of course motivated through the classic consideration that every dependence towards a class is a good reason for that class not to change, while accounting for the fact that extensions are very strong dependencies.
Copy paste rings another bell, in the first pass I notice that there’s copy paste, I might be at the beginning of a combinatorial explosion and SRP would lead me to understand there are two axes of variation evolving and that I should move inheritance to composition; but the bell rang in the first place thanks to a lot of excellent points (DRY is a good grouping of them) and the list of known smells, so instead of proceeding with the next test I ask myself : “Why did I copy-paste this?”.
As I said in my post to the XP list, it certainly draws my attention the act of changing a given class for the third time. Yet there are lots of reasons to do that, first the heuristic will attract my attention to what I’m doing, then design principles will let me find out the real, design-level cause, while agile (manifesto) principles will help when it’s not due to design at all, along with a plethora of other principles which all have gathered into common sense.
A somehow more concrete example to sum this all up.
Let’s suppose I am writing some solution code and I’m changing a class which is a the root of a hierarchy.
I know that there should be preciously few reasons for this class to change, yet it is changing.
Suddendly I stop and say :
Why am I changing this class once more?
I might have a real issue in my hands, so I note it and finish the change. While refactoring, thanks to that hint I’ll stop on that code and ponder. More questions might arise, depending on what I see and what “second-level” oddities I notice, here are some examples of the analysis that might follow.
Case 1 : I am adding yet another “if” to my method. Definitely a conditional logic smell. “Am I performing extension through modification? Should I think about a hierarchy instead?”. OCP is talking here. This smell might still be the result of a different issue yet, I might be defending against a specific behavior of a type I am invoking [if(foo instanceof Gas) foo.compress())]. “Am I suffering lack of substitutability?”, Liskov would tell me, but then I might notice that I failed to properly mantain the abstraction because the interface is too complex, and I would notice this because I know that not well-segregated interfaces produce this sort of abstraction leaks.
Case 2 : It just happens that out of three behavior changes requested by the customer all of the three do fall in this class, in three different methods. “Has this class too many reasons to change?” SRP would produce this second level question.
Case 3 : I changed Class A and in this class I use A, along with B and C, which were the cause of the previous changes. “Is this class implementation coupled with other concrete implementations I should abstract?”. Dependency inversion is producing the question this time.
Case 4 : I have a template method structure in place between the base class and its extensions and a genuine need by the customer to update the desired high-level behavior which is, by all rights, located in the base class. None of the design principles here is ringing any bell for second-level questions. The best way to mantain coherence is to indeed update the logic in the base class (trying to override high-level behavior from a subclass in a template method might result in substitution inconsistencies) , so I’ll mantain this simple, but so far adequate pattern, and address the real issue, which does not seem related to design.
I ask myself “Do I communicate well enough with the customer?” Am I helping him properly define what he wants? Is he still trying to find what he wants?”. These all come from valuing communication, if I had different principles in this context I might instead ask myself “Is the specification clear enough?” and get to very different solutions, how sad…