I had a very interesting discussion at GOTO Chicago with the awesome Corey Haines, software journeyman and founder of the Code Retreat movement.
We were talking about the “Spike and Stabilize” pattern, and whether it was a good idea to suggest (as I have) that experienced TDDers can skip TDD under situations of flux and high uncertainty. I still maintain that you have to be really good at TDD to write spikes and stabilize them, and agree with Corey that most developers will get more traction from throwing away their prototypes once they have feedback, and start again “properly”.
However, I think the skill of being able to take existing, messy code, clean it up, and bring it under test, is hugely important.
Developers aren’t born knowing how to do TDD effectively. New developers will always be coming into our industry, and they’ll always be making a mess until they learn how to make things clean. If you can write your tests first, that’s absolutely fantastic! But if you can’t – or if someone on your team can’t – then being able to tidy up the code, and being able to show others how to do it, is IMO even more important than getting it right in the first place… and it’s not just novice devs who make the messes, either. We’re all human.
So if you can’t write tests first, at least write tests second. Then try writing them first next time.
“If you can write your tests first, that’s absolutely fantastic! But if you can’t … then being able to tidy up the code …”
The seems a false assumption here.
The false assumption being that doing TDD generates code that doesn’t need to be cleaned.
This is completely contrary to TDD. In TDD you do the simplest thing possible to pass a test: that usually turns out to be a complete dog’s dinner, usually massively duplicated with inscrutible single-letter variables and requiring at least some method extractions if not entire class extractions.
That’s why the crucial phase3 of TDD is, “Refactor.”
I see that, “Refactor,” as what you see as tidying up the code.
In other words, you can’t do TDD unless you can tidy up code.
So when you say, “if you can’t [write tests first] then being able to tidy up the code … is IMO even more important than getting it right in the first place,” well, that just makes no sense to a TDDer.
Or are we dancing around the old write-tests-firsts vs TDD distinction?
There’s a difference between refactoring as part of TDD and refactoring in order to bring a legacy code base under test in the first place. The second comes with a whole host of other problems, like being able to understand the behaviour of the code when it’s completely lacking in the usual documentation (ie: unit tests).
But you’re right, I’m conflating the issue, as well as mixing it up with the fact that when I do BDD at a unit level I tend to write pretty nice code (because I’m thinking about the responsibilities of the classes, etc.).
I think of the TDD cycle as producing good, clean code at the end, regardless of whether it happens that way because of nice BDDism or because we clean it up one small bit at a time. I would love people to have the ability to produce good, clean code when there’s more than just a small bit to clean up, and I believe that skill is different, and hugely important. Thank you for prompting me to clarify my thoughts here.
Pingback: Geek Reading April 25, 2013 | Regular Geek
The more I work with legacy code, the more I recommend the “incremental rewrite” style over refactoring code in place. This means picking a small part of the system, understanding what it does (or ought to do), test-driving a replacement for it, then rerouting requests from old to new. Of course, I recommend doing this in small batches in order to make steady progress and avoid falling into the trap of rebuilding more than we need. Thus, the emphasis on “incremental” over “rewrite”.
Because of this, I find the spike-and-stabilise pattern troubling in the large and manageable in the small. By this, I mean that so long as one keeps the spikes quite small, one can get away with stabilising it with tests after the fact. I can’t easily define “small enough” here, as I imagine it depends on a host of factors that I can’t enumerate well enough to try. Perhaps one learns “small enough” from experience. (Does that make the whole statement beg the question? It might.) Perhaps I do better to say the size of thing one can safely spike-and-stabilise varies from programmer to programmer, and most programmers most of the time overestimate what they can safely spike-and-stabilise, so I urge them to err on the side of spike-and-rewrite.
How to “increase” the size of thing one can safely spike-and-stabilise? Build more stuff well, observe your blindspots, notice your design tendencies, write code in reversible steps, use version control aggressively, reach the point where you can see 10 tests ahead. You’ll probably still write the tests in your head, or at least sketch out the sets of tests in your head, but not write them. You’ll use other techniques to lower the cost of mistakes. It’ll be fine. I feel comfortable doing this now; I refactor without tests quite a lot; it took me at least five years to become even the slightest bit comfortable with it. Everyone differs in how long it will take. Everyone probably underestimates how long it will take. Everyone probably overestimates how ready they are. 🙂