Jason Gorman put up a video on the Open-Closed principle out on his blog today. I claimed on Twitter, that he was doing a refactoring while having a red bar. Over the discussion, I decided to put up the way how I would have developed the fibonacci extension while refactoring on a green bar on my blog.
This is the class, which we are going to extend:
public int getNumberAt(int positionInSequence) {
if (positionInSequence < 2) {
return positionInSequence;
}
return getNumberAt(positionInSequence – 1)
+ getNumberAt(positionInSequence – 2);
}
public int[] getSequenceOfLength(int length) {
if (length < 8 || length > 50) {
throw new IllegalArgumentException();
}
int[] sequence = new int[length];
for (int index = 0; index < length; index++) {
sequence[index] = getNumberAt(index);
}
return sequence;
}
}
It computes a sequence of fibonacci numbers. We want now to add a customized class, which remembers the previously computed sequence length. Of course, we start this with a new test case for our new class:
@Test
public void whenSequenceIsRequestedSequenceLengthIsStored() throws Exception {
FibonacciNumberGeneratorWithHistory fibonacciGenerator = new FibonacciNumberGeneratorWithHistory();
fibonacciGenerator.getSequenceOfLength(8);
assertEquals(8, fibonacciGenerator.getLastSequenceLength());
}
}
and the according implementation to make this project compile:
public int getLastSequenceLength() {
return 0;
}
}
Up to this point I exactly followed what Jason was doing in his video. Now, the thing that I would do differently. The currently default return value of 0 in our new getLastSequenceLength() method, should be 8 to make the test pass:
return 8;
}
This is not pretty, but does the job. Now, with a green bar, I won’t triangulate the behavior, just as Jason explains in the video. Instead I would now refactor to get to the behavior I want. So, let me introduce a new field where I store the previously calculated sequence length later:
This is now a parallel design, which I start. I learned this from Kent Beck during the Responsive Design class last week. Running the tests tells me that I didn’t introduce any problem. So, let me continue with overriding the getSequenceOfLength(int) method:
public int[] getSequenceOfLength(int length) {
lastSequenceLength = length;
return super.getSequenceOfLength(length);
}
The tests are still green at this point. Now, that I finished my parallel design development, I can switch the getLastSequenceLength() method to use the field I introduced:
return lastSequenceLength;
}
Tests still green, and I’m done. I even reached the same implementation as Jason, but instead of refactoring on a red bar, I designed a parallel implementation, and had the confidence of the green bar all the time. Feels much safer to me.
I should probably point out that I too would normally lean towards starting with returning a hardcoded 8 and then maybe generalise after another example (triangulate) but explain in the video that I’m not doing that for the sake of brevity.
I’m not convinced that replacing a single hardcoded literal with a general solution counts as refactoring, TBH.
Well, I could have made it more explicit, by introducing a local variable which is set to 8, turning that into a field, then overriding the function, and storing the actual value in it, and initializing the field to 0. I felt brave to do larger steps in this example, as it seemed to be “obvious” for me to do.
It’s not really a refactoring, but a separate design that I evolved while being on the green bar. That’s right.
I really believe that if you do something you did before like introducing a hardcoded value, then replacing it by a simple coding like here is a violation against the DRY principle. We all know that this triangulation is safe. We know it because o our experience.
Often I see introductions to TDD ignoring the experience of developers. They really feel fooled. Of course there are many many excellent reasons to do it and especially teach it without triangulation. But life is different from exercises.
An analogy: We all drive a car. Some of us often, some of us rarely. We all know that we should check the tyres, the oil as well as other important aspects of the car. – Do we do that every time before we drive? – No.
But when preparing a big travel by car, maybe a few hundred miles, we do these checks just to be safe not to experience problems on our way.
To me this is like: if you do routine coding, triangulation is ok. If you do something more complicated go with small steps. The way you code just should be suitable to the intention.
I complete agree to you, Andreas. My point is, that I would like to make these steps while having a passing test. I could have test-drived the new implementation, the parallel, but I refused to do so, because I found it easy enough to implement.
Still I decided to work on the green bar further, rather than having a failing a test too long. Could be some of the remains of the “try to get a red bar as rarely as possible” exercise from last week’s course, but the value the tests provide me in feedback is worth for me to do so.
On a second note, I appreciated the way Kent taught us TDD as an advanced course. Rather than “you must not do this now”, our thoughts were provoked by trying out different variations. If you teach TDD to someone new to it, on the Shu-Level, you want to keep this as tight as possible. Over time, with experience in TDD, you can dare to make larger steps. The reason why the textbooks do not mention this, is that they were (thus far) primarily aimed to the novice, rather than the more experienced. Maybe it’s time for a book on advanced TDD.
Or beginners-TDD for advanced programmers who never did TDD before.