- Consulting / Coaching
- Jeff’s Blog
I do TDD mostly to support the ability to keep my code and design clean, with high confidence that I’m not breaking something in the process. Most developers who do not practice TDD refactor inadequately, because of the fear based on the very real likelihood of breaking the software. I can’t blame ’em for the fear. I’ve helped developers in environments where defects related to downtime had the potential to cost the company millions of dollars per minute.
“Inadequate? How dare you!” Yes, inadequate. The best TADers (test-after developers) claim around 70%-75% coverage. The problem is that there’s usually some good, chewy, gristly meat in that 25% to 30% that isn’t covered, and is thus not easily or safely maintained. Costs go up. The system degrades in quality by definition.
In contrast, I have very high confidence that I can move code about when doing TDD. I can also improve the structure and expressiveness of a system, making it far easier to maintain.
Duplication is sooo easy to introduce in a system. It’s harder to spot, and even harder to fix if you don’t have fast tests providing high levels of coverage. I’ve seen one real case where introducing close-to-comprehensive unit tests on a system resulted in shrinkage down to about 1/3 its original size over a reasonably short period of time. And with most systems I’ve encountered, I can scan through the source code and quickly spot rampant bits of unnecessary duplication.
Good code structure and expressiveness is also lacking in most systems. If you’ve been a developer for more than a few weeks, it’s almost a certainty that you’ve spent way too much time slogging through a multi-hundred or multi-thousand line long function, trying to understand how it works and how to fix it without breaking something else. In a well-structured (cohesive) system, the time to pinpoint and fix things can be reduced by almost an order of magnitude.
TADers simply don’t eliminate duplication and clean up the code to this extent. It’s not a goal of TAD.
Which would you rather maintain? The TAD system, or a system that’s half the size and double the expressiveness?
There are many other reasons TDD allows me to go faster than TAD. The converse of my “why TAD sucks” reasons should hint at many of them.
As mentioned two posts ago, it’s not possible to see new comments made on my old blog. Here’s a comment I received on Tradeoffs.
The basic thrust of the “Tradeoffs” blog post is that code & fix cycles–after development thinks they’re done, and software gets run through a test team–we typically hit a cycle where QA encounters no end of problems that must be fixed.
“I think you’re comparing a project that uses TDD to a project that writes no unit tests until, ‘Done,’ right?
“Why are you ignoring the projects that write unit tests after each unit is written?
“This is faster than both the approaches you describe. It’s faster than TDD because you write far fewer tests that you don’t need and faster than your other (waterfall?) approach because you find problems sooner.”
Schmoo asks a good question–writing unit tests shortly after a “unit” is written could conceivably be a way of minimizing the code & fix cycle.
I’ve seen it tried, however. In reality, it helps a little, not a lot and not nearly as much as TDD. The code & fix cycle is still there, a bit shorter than it would have been otherwise.
Now, add good automated acceptance tests into the mix, and I believe Schmoo’s premise is closer to true. But unit tests? No. The primary reason is that most test-after-development (TAD) unit tests are insufficient with respect to coverage, particularly in the more complex areas. Why? Check out my earlier blog post, The Realities of Test-After Development.
As far as TDD generating more tests that you don’t need, depends on what your goals are. I’ve found that TDD, which requires you to write tests for just about everything, results in much faster development than TAD, even as TAD produces less coverage. More on these topics in a future blog post.
I recently responded to a poster at JavaRanch who was asking about how stories are used in agile. I think a lot of people use the word “story” improperly–they use it in the sense that a story is a feature. “I built 10 stories into the product.” No, a story is a discussion around a feature. A story is also not a card.
While this may seem like a nit that I’m picking, I believe misuse of the word “story” has led to unfortunate insistences, such as rigid formats for cards that capture feature conversations (and I’m going to stop calling these cards “story cards,” which isn’t quite as bad a term, but it does help reinforce the wrong things).
(Most of the rest of this post is taken directly from the exchange on JavaRanch.)
“Story cards” are simply placeholders, or tools. The word “story” is the important thing here. A story is something we tell people; the card is simply a reminder, a summary of that oral storytelling. A card is a convenient medium. I suppose I’m thankful I don’t hear about “story whiteboards.”
Story cards (I tried… it’s not easy!) are not artifacts! Once you complete work on building a feature, the card is meaningless. You might think you could use it to track what had been done in a given iteration, but a 5-word summary of a several-day conversation (between customer and developers and other folks) scribbled on a card will not likely be meaningful to anyone six months down the road.
Instead, we get rid of the conversation piece, and replace it with something that documents how the system behaves once that new feature is in place. The best form for that document is a series of well-designed acceptance tests that anyone can easily read and understand. (See our Agile in a Flash card on Acceptance Test Design Principles for a definition of well-designed.)
Acceptance tests are the closest analogs to use cases (a tool popularized almost 20 years ago now), but with slightly differing granularities. The name of an acceptance test can map to the goal, or name, of a use case; it can also map to a much smaller slice of functionality. This is because features in agile are intended to be very small, taking only a day or so to complete. So someone might tell a story: “allow users to filter sort results by age.” That’s likely not an entire use case, it’s an alternate path on a larger use case.
Otherwise, the focus is the same: Jacobson said you could produce user guides and documentation from well-written use cases. The same can hold true for acceptance tests (with a little assembly work), which have the vastly superior quality of eternal accuracy: As long as all your acceptance tests pass, you know that they describe how the system works.
Stories, on the other hand, are just conversations, and the memory of all conversations eventually fades from memory. The cards act as great tools while the story is in progress–they help us track things and remind us what we were talking about, but other than that, we should just recycle the cards once we deliver the corresponding features.
I converted to WordPress some time last year, since Blogger killed my ability to edit there and post on my personal site. In the meantime, I have two annoyances: One, the spammers to this blog are unrelenting. You don’t see any of the spam comments, but I get an email for each one (it could be a real comment) and have to whack about 25 spam messages a week.
Two, while it looks like it’s possible to add comments to my old blog entries, they really just disappear into a bit bucket somewhere. I might dig around and fix the page so it no longer supports typing in the comments, but in the meantime I’ve had two comments that I wanted to respond to, and have felt guilty because I can’t do so easily.
Perhaps my responses will end up as blog entries.