This article is part 2 in a series in which I’m doing a well-known code kata in the “TDD as if you meant it” style. I’m also strictly following the 4 rules of simple design and looking for opportunities to use implicit coupling to drive refactoring under the Once And Only Once rule.
A the end of part 1 we had this code:
It’s now time to REFACTOR, which I consider to be very much the hardest part of TDD. How should we proceed?
Well, first let’s make a list of the problems I can see in this code.
Our intention is that these two tests should be testing the same thing. That is, we expect them to be coupled, but they aren’t. There’s nothing preventing me changing the tests independently.
The story told by the first test now seems out of date compared to the second, because in the first test there is no mention (in the code) that no items have been scanned.
There are zeroes on lines 3, 4, 5, and 9 — they are all the same zero, but the code does nothing to hint at that.
There are 50s on lines 8, 11, and 12. Again, these are all the same 50.
I’ve probably missed some places where our intention isn’t expressed explicitly, or where some coupling is not explicit. Did you see any I missed?
Let’s assume that my list above is reasonably complete. I don’t think it matters if it isn’t, because my perception of the code will change as soon as I make any changes. So we’ll be constantly revisiting and refining our list until we decide it’s time to move on. The important thing now is just to make a start at improving this code. So, where to begin?
At this point I find myself hesitating. Does it matter which order I make these refactorings? If I change A then B, will I get the same code as if I had changed B then A? Is there a “better” choice? If I do A first, will that back me into a corner such that it will later be harder to do B? Is there an “optimal” sequence in which to make these changes?
I do this all the time, and it’s mostly futile. I think it’s a personal hangover from the days before TDD — I still haven’t completely lost the habit of wanting to “solve” the problem before I write any code. What would you tackle first? Make a note before reading any further.
So, a quick slap on the wrist and I remember to go back to the 4 Rules. My top priority — at least according to my personal reading of the 4 Rules — should be to express intent. There are several ways in which the current code doesn’t do that, and I’m going to pick consistency first. Both tests should be telling the same basic story, with the only differences being that ‘A’
and the corresponding 50. So I want to change both tests so that they tell the same “shape” of story using the same terms.
First, I change the second test so that itemsScanned
reflects the plurality of its name:
Now I can change the first test to tell a more similar story:
The tests are green, so I commit: Make the tests more similar shapes
.
This feels clearer, but I’m still not happy that our intention is being well expressed. I find my eye repeatedly drawn to line 11, which has no counterpart in the first test. And because of that, I’ve just had an idea; an idea that I really like…
I want to follow the 4 Rules, so I want to change this code to express my intention before I tackle any violations of the Once And Only Once rule. In fact, for the sake of intentionality I’m going to make the duplication worse now. And all because line 11 is an asymmetry between the two tests.
Another way to read line 11 is that it adds 50 to the current price if there’s an ‘A’
in the scanned items list — in fact, if there’s anything at all in that list. So first I rename that price
variable to reflect the verbal language I’m using when I discuss the code:
(This is an important step, because it helps to reduce friction and cognitive load. If we’re using one set of vocabulary when discussing the domain, the code shouldn’t be using a different set of vocabulary.)
Next, I tell the story above in code:
I think this test now tells the story we want, and does it reasonably simply. And now I can tell the same story in the first test:
Commit: Tell the same story in both tests
.
It’s worth noting that I’m leaning quite heavily here on the fact that the 4 Rules are a set of priority calls. I’ve added unnecessary stuff to the first test (violates the 4th rule) and I have apparently increased the duplication between the two tests (violates the 3rd rule). But because the 2nd rule takes precedence I’m allowed, nay encouraged, to do that. And what’s more, we can now see the implicit coupling between the tests quite clearly, expressed as duplicated code. That duplication was always there, but previously it was hidden somewhat by the different ways those two tests told their stories.
Next time I’ll probably invoke the Once And Only Once rule on these tests. Although it’s often foolish to make predictions…
Things to try
Make a list of problems you can see in your own code, and prioritise them according to the 4 Rules.
Make sure the names you use in the code reflect the vocabulary you use when talking about that code.
Find a place where your code doesn’t tell the story you and your Customer intend; refactor until it does.
As always, if you try these please share your experience with us all by leaving a comment.