January 20, 2013

...Learn TDD with Codemanship

The Mysterious Art Of Triangulation

I've started the New Year by introducing my apprentice-to-be, Will, to Test-driven Development.

I think the mental exercise will be good for learning programming languages (our first session uncovered some interesting Python quirks, for example), and I think when he starts his degree studies - which will likely involve writing at least some simple software - he'll benefit from the basic discipline of it.

I recall as an undergraduate - and as a newbie professional - trying to implement relatively simple algorithms, and all those late nights staring at the screen wondering why it wasn't working. If someone had showed me TDD before I started all that, I could have saved myself a lot of frustrating debugging time. Which, as we all know, is the antithesis of valuable drinking time.

Will has hit a pitfall I see a lot - let's be honest, most - fall into when they start learning TDD.

He's doing FizzBuzz (any why not?), and he started something like this:

Test #1 - First element is 1.
Solution - return array [1]

Test #2 - second element is 2
Solution - return array [1,2]

Now, this is indeed the simplest thing he could do to pass both tests. But, is solution #2 a step forward towards a more general solution? Or is it a step sideways to a longer hardcoded solution?

Fast forward a few unit tests, and we find that the solution [1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz] is still simpler than a general algorithm that might produce the same sequence.

Do we keep hardcoding the sequence? When do we generalise it to an algorithm that generates the same sequence? When the sequence is made up of one more character than the algorithm?

That would be silly. If we hardcoded up to, say, 40 and then decided that an algorithm would be shorter than hardcoding up to 41, we'd have to write the entire algorithm in one go.

In TDD, our goal is to discover the design in baby steps, verifying that it works and cleaning up our code as we go.

I like to use the analogy of trying to cross a river using stepping stones. To get to the other side, you want each step to take you closer, whilst at the same time avoiding risky leaps that might see you fall into treacherous water.

To me, "forward" in TDD means closer to a general solution. If we keep adding array elements, or branches, or appending to the same string (and so on) for each new test case then that's like we're moving downstream from one stepping stone to the next, getting no closer to the opposite shore. Then we end up having to make one giant, potentially very risky leap at the end to reach it. Ideally, we'd want to end up on a stepping stone that's right next to our goal, so that final step is just another baby step.

Of course, the other danger is overconfidence. We're not so hot at judging distances, it seems. And some developers - particularly inexperienced ones - are tempted to think they can leap the river in a single bound.

The discipline of triangulation is to balance taking the smallest steps you can with making good progress. You want to get there safely, but you do want to get there.

Posted 3 days, 12 hours ago on January 20, 2013