December 1, 2014 | John Rusk | 5 Comments 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. Wrap-Up 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.
Nice! Overall, it’s a very interesting solution. I’d be happy to have written it. As you say, it probably violates someone’s coding standard somewhere but everything we do violates someone’s reading preferences, so that’s OK. Some observations: 1. I read from the top, so I didn’t realize the implications about width being quite different from “the last letter wanted” until I finally read the tests. This tells us more about me at 5 AM than anything else, but it makes me wonder if my articles would be best with the test at the top (as well as discussed first if I do them first). 2. My reading started right off with a feeling of constructing the picture very directly. Even though I didn’t really fully understand it, I already had a very pleasing sense of “here we go, one thing after another, everything going well”. Oddly, when I got to the negating of the increment, that stopped me cold for a bit. What was going on? 3. To fully understand how it works, I wound up kind of hand-simulating it to see what the values did. Somehow, though it should have been, it was not obvious to me quite how it worked. I think there’s a trickiness in looping on distance from centre and letting it first grow, then reverse, then finally stopping when it goes negative. That makes me wonder whether a version that simply looped row from 0 to n, and computed the letter positions from the values of centre and row, would be more clear. It’s possible that making the letters run out and back in would still be obscure that way. 4. When I write these things, I try to describe how the solution grows. The effect is very different, I think, from coming across a solution that’s done. I’m interested in what goes on in the 18 minutes while one is coding. That said, it’s very tedious and long to write those things. It’s also rather a pain to distract oneself from being immersed in the coding to write down what one thinks. Mind you, I’m not saying you should do that. I’m just observing the different effects of different ways of writing about these things. 5. To expand on that: we could surely polish that gem much more. Imagine we were going to put it in a magazine as an example of a really nice solution to the Diamond Problem. By the time it was polished, it’s likely that how it was created would be completely obscured. The reader would see this fine piece of code and might have no idea how to produce something like it. And, of course, our reconstruction of how we did it would be almost entirely made up after the fact. By the time we’re done, we don’t know what we did either. 🙂 Overall, I found the article interesting and would have even if I’d not just worked on the problem myself. It raises some interesting questions about up front vs as you go, and little methods vs large, especially for algorithmic things like this one. I fully agree on “one true way”. To me, the thing is to know many ways. If I can master many approaches, I can choose among them. If I limit myself to only one approach, my solutions will surely be limited as well. Good stuff, thanks!
Thanks for your comment Ron. Interesting note about the reversing of the increment. The (loose) metaphor that I had in mind at the time was of something “bouncing” off the edge of the containing rectangle, and reversing an increment is a pattern I’ve used for bouncing in other contexts. That metaphor helped me, but obviously isn’t as clear, from the code alone, as it was in my head at the time. I did contemplate explaining it in the article text or a header comment – but felt it would be a bit like explaining the punch line of a joke: if you need an explanation, you probably told the joke wrong! Its a good point you make that there would have been other ways to compute the distance from centre without “bouncing”. I like your summary, that this version of the code raises questions about method size and timing of thinking. Of course, to answer those questions would be to fall into the trap of proposing another “one true way”… but to ask them seems useful.
Not big at all: With APL, figure out the boolean mask, the bands of letters, and apply the mask {(⍵{⍺=1++/|⍵}¨(⍳2/¯1+2×⍵)-⍵){⍺:⍵ ⋄ ‘-‘}¨( ∘.{⍺}⍨{⍵,1↓⌽⍵}⍵↑⎕a)} Most small problems are too small for one to bother with tests.
An alternative APL solution: {{⍉⍵⍪1↓⊖⍵}⍣2⊢⌽(1+-⍳⍵)⌽⍵ ⍵↑⎕A,⍤0 1⊢⍵⍴’-‘} Explanation: [⍵] is the right argument [⍵ ⍵↑⎕A,⍤0 1⊢⍵⍴’-‘] is an ⍵x⍵ matrix of the letters A-Z followed by ⍵-1 dashes [⌽(1+-⍳⍵)⌽…] rotates each row by 1 more, putting letters on the diagonal [{⍉⍵⍪1↓⊖⍵}] is a function which catenates a matrix to its reverse along the first dimension, losing the first row, and transposing the result [{…}⍣2] applies the above function twice
P.S. You can copy/paste the APL expressions into https://tryapl.org, adding a right argument of the letter count. For example, {{⍉⍵⍪1↓⊖⍵}⍣2⊢⌽(1+-⍳⍵)⌽⍵ ⍵↑⎕A,⍤0 1⊢⍵⍴’-’} 3 –A– -B-B- C—C -B-B- –A–