I conducted an experiment today. I chose a problem which Ron Jeffries solved with TDD. I took the opposite approach. I sat for about 5 minutes and thought about the solution. Then I wrote down the code, added a unit test, ran the test to find the errors (there were 3), added one more test, re-ran both tests, and I was done.
What did I learn?
- I’m reasonably happy with my “think first” solution.
- I like it because it represents the solution in a very direct way. It’s something my mind can relate to. The design embodies a “Unit Metaphor”. I just made that term up ;) I mean a small-scale version of XP’s System Metaphor – a way of thinking about this unit of code that makes sense to me, as a human.
- I don’t think I would have come up with such a direct solution if I’d worked test-first. I believe I would have been led to the solution in a much more round-about way, and vestiges of the journey would have remained in the final code.
- During TDD the code “speaks” to you. But I question whether it speaks with a sufficiently creative voice. Can it really “tell” you a good Unit Metaphor? Or does it merely tell you about improved variations of itself? If the Unit Metaphor is missing at the start, will it remain missing for ever? (And it probably will be missing at the start, because as a good TDD practitioner you deliberately didn’t think about it at the start, right? ;)
- As an aside, maybe this example problem is too small. Ron got a 6000-word blog-post out of it, but its it really a big enough problem to serve as a test-bed of design and coding techniques? Maybe our online discussion about TDD is skewed by the inevitable necessity to use relatively small examples. I don’t know….
What I do know (or at least strongly believe ;-) is that a certain degree of directness helps humans understand code, and a little up-front thought may help to create that directness. The trick, I suggest, is to seek a simple Unit Metaphor during your up-front thinking.
The Design Problem
The problem posed was to write code to create textual output in a “diamond” pattern, like this:
- - A - -
- B - B -
C - - - C
- B - B -
- - A - -
(spaces added here, just for readability).
Obviously it should be parameterized, to produce diamonds of various sizes. The next size up has a “D” line in the middle, surrounded by two “C” lines.
This coding problem was previously mentioned by Seb Rose and Alistair Cockburn.
Comparing the Solutions
(If you want to try writing your own solution, best to do that now, before following the links to Ron’s solution and mine).
Ron’s solution is in Ruby. You can find it at the bottom of this page.
My solution is in C#, since that’s the language I know best. You can find it, and the two unit tests, in this text file.
Comparing the two, Ron’s looks more visually appealing at first glance. The methods are shorter, like methods are “supposed” to be, and it’s doing some clever stuff with generating only one quarter of the output and using symmetry to produce the rest.
Mine looks uglier. The implementation is one 24-line method. (I think I’ve violated a few published coding standards right there!). But it does its work in a very straightforward way. It builds up the diamond one complete line at a time. It directly models the current width of the diamond, by keeping track of the edge’s “current distance from the centre”.
My, totally biased(!), view is that the direct, single-method implementation is actually easier for humans to make sense of and reason about.
BTW George Dinwiddie posted another solution here.
An Aside About Timing
It’s worth noting that my initial 5 minutes of thinking produced the general shape of the solution, but not all the details. The actual coding, including the two tests, took about 18 minutes – an embarrassing proportion of which was consumed by the three bugs and with details of C# that I really should have known (e.g. I felt sure there was a built-in “IsOdd” method somewhere for integers. But apparently there’s not.)
I think I would have taken longer to produce a solution with a pure TDD approach. Of course, I can’t prove that because, as Ron points out in his post, its impossible for one person to realistically test two different approaches to the same problem – since any second attempt is polluted by knowledge gained in the first.
For the record, I also enjoy test-first. Particularly on really complex problems, or on simple ones when I’m suffering from writer’s block.
What I object to, and feel uncomfortable with, is the common implication that there’s only one true way to build software. People differ. Projects differ. Elements within projects differ. We should embrace those differences, and draw on our full range of tools – including up-front thought.