If you are not doing TDD…
…it’s fine, don’t worry. You don’t have to. Watching Dan North sessions on QCon (you can also download them from the recent NDC 2012) finally helped me to not feel like a shit when I am not doing it. Long story short, Dan suggests that there are valid reasons and cases where TDD may not be the right/only valid choice. He compares costs and benefits of TDD and spike-and-stabilize (i.e. hack some code and if it makes sense, make sure it’s working). I’d like to add another approach and discuss them all together: test/specify before.
I understand why Uncle Bob is constantly yelling at us about TDD. This way of working is still pretty much unknown to most of developers and deserves a lot more attention. If you are a seasoned developer however, you probably realized that this is only one side of the coin. In real world projects writing every single line of code in test-driven manner is a pure waste of time and leads to overall frustration.
Why? TDD is most useful when you have absolutely no idea about the shape of the code. In such case you better not hack the code as fast as you can. Rather then make one small step at a time, each time validating the outcome. Ideally, do it in a pair. Each step you make allows you to discover more tests that need to be written. I love the analogy Dan used in his presentation: it’s just like swimming versus walking in 1.50 meter deep pool. You can walk all the way through ensuring every time one of you feet is on the ground or you can just swim which is more risky (no contact with ground) but makes you move a lot faster. TDD is like walking.
Spike-and-stabilize on the other hand is like swimming. It makes you go faster but less safe. Of course as each and every analogy, this has some flows. One is, it does not show the cost of stabilizing. It’s true that for some time you can go faster, but then you have to pay the cost in stabilizing the whole thing (writing tests after). Other thing is, you can’t just go with this approach forever as lack of tests will slow you down after some time. But spikes should be short anyway, right?
Last but not least, both TDD and S&S imply there is some design work to be done. As Ayende pointed out some time ago, there should be absolutely no design work involved in implementing new features. Every interesting (from code design point of view) thing should have been done in the (very small) infrastructure part. It means that if your architecture is defined well (based on the functional requirements) and you coded it all into the infrastructure of your solution, you probably won’t need to do neither TDD nor S&S while implementing well-understood features (the ones that you expected to come in some way). Just take a user story from the pile, write some specifications and implement the damn logic.
By the way, you can replace the vague term of ‘architecture’ here with any well known set of patterns and practices. Take domain-driven design as example. If you decide to use domain model group of patterns to implement you core business logic, then there is no place any code design. Why? Because you already have a model (the design) and the recipe how to code it (the patterns). You can proceed right away with writing specifications/test and then implementing them as aggregates, entities and value objects.
The example of scenario where all the approaches just described combine is implementing a completely new feature (one you didn’t even think of). You can start with some spikes to investigate the possibilities. If one of them looks promising, you can stabilize it by writing some tests. For now the just implemented feature does not fit into the architecture/infrastructure of the whole solution. It is an exception, but since it is once single case, you don’t bother. When a next similar feature comes, however, you need put some effort to extract the infrastructure code from the previous one and make it available to all other features. You probably want to do it in TDD rigour to ensure the highest possible quality. Last but not least, you implement the new feature using test/specify-before approach as there is absolutely no more design work to be done.