The most common problem we face when writing software - a problem that transcends the community around any one library, platform or language - is complexity.
Simply put, the complexity of the software we want to build exceeds the capacity of our brains.
This isn’t particularly surprising, given the oft-quoted maxim that we can hold 7 +/- 2 items in our brains at one time. When you note that relatively simple pieces of software can be tens of thousands of lines in size, and complex software can easily run into millions of lines of code, the surprise isn’t that we fail to hold the entire system in our heads at one time but instead that we are able to build these systems at all.
To be successful at building more than the most trivial of systems, we need ways to reduce the inherent complexity of the system to manageable levels.
(Aside: given the poisonous nature of complexity - too much of it can kill a project dead - it’s a consistent surprise to me how some developers insist on making systems more complex. Sometimes this manifests through needless abstractions and over engineering, other times by conflating different concepts into one, and sometimes by forcing a particular shape or style. Software development is difficult enough without adding addition complexity on top of the natural complexity of the problem we’re solving.)
Test Driven Development is one technique for reducing complexity - an especially powerful one.
Done properly, TDD provides both guidance on what code you need to write next (the code to make the latest test pass), and a safety net of tests to ensure that todays code written to satisfy todays goals doesn’t break the code you wrote yesterday to satisfy yesterdays goals. This safety net serves to lift a significant complexity burden as the developer no longer needs to remember every detail of the past; they can simply concentrate on solving todays problems.
Perhaps the key challenge is this: How do you choose the next test to write? Indeed, the choice of which test you write next is described as a critical part of the art of test driven development.
Choosing the wrong next logical test can easily guide you into a dead-end implementation where it’s difficult to handle the next piece of required functionality.
When you run into that wall, what do you do? Do you spend a long time refactoring the code from one style into another, potentially spending hours or days just to make one test pass? Or do you throw away what you have and start again, repeating the same tests in a different order and hoping for a different outcome?
Surely the best approach is to avoid the problem in the first place.
This is where experience comes in - and the consideration I mentioned in the title of this piece.
Generally, the more experienced you are as a developer, the earlier you will be able to see the shape of the final solution. Even if only dimly perceived, paying attention to that shape - giving due consideration to it - will help you choose the best next test to write. (This doesn’t mean that you will necessarily end up where you expect, but that’s another story.)
The danger of seeing the destination clearly is in the temptation to write too much code in one go.
- You are not allowed to write any production code unless it is to make a failing unit test pass.
- You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
- You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
There’s an old story about a traveller walking on a dark and stormy night, heading to the town train station to continue his journey. The lamp he carried in his hand helped him to see where he was going and to keep his footing, but did nothing to help know where to go. The lamp hung outside the station office could be seen from a distance and helped him to see where he needed to go, but didn’t light the ground at his feet. Only with both lamps could he travel safely.
Test driven development helps you write reliable code that works. It helps you keep your footing.
But, you also need an idea where you are going.