TDD is Debugging Features Into Existence

Two books I’ve read recently, Making Software and Beyond Legacy Code, have touched on Test Driven Development and made some interesting points I hadn’t thought of before. In Making Software they bring up addictiveness:

Could it be addictive? Personal communications with TDD developers suggest that it is an addictive practice. It changes the way people think and their approach to coding in a way that is difficult to roll back. Therefore, leaving TDD practices may be as difficult as adopting them.

Oram, Andy; Greg Wilson (2010-10-14). Making Software: What Really Works, and Why We Believe It (p. 215). O’Reilly Media. Kindle Edition.

From my personal experience and the people I’ve talked to, TDD/BDD is absolutely addictive. I’ve been lucky to experience working professionally in two completely different environments in respect to testing. For the first programming job of my career, I knew of tests and what little I wrote, I wrote in a test-after fashion. The process there was your typical: think about a problem, come up with some solution, type it in, run it, see what doesn’t work, fix that, and repeat. As you can imagine, I got very good at debugging there since that was what I spent most of my day doing.

At my next job as the second engineer at SideTour, though, the entire app had been test driven from day one. I was even able to pair full time with the CTO and learned how to do it effectively. I picked it up quickly and was immediately hooked to the point that I test drive all of my side projects as well.

Bugs Are Missing Tests

I’ve wondered for a while now how I picked up such a different process so quickly, and it wasn’t until I came across Beyond Legacy Code that it really started coming together. In the chapter “Bugs Are Missing Tests”, David Bernstein says:

Every bug exists because of a missing test in a system. The way to fix bugs using TDD is first write a failing test that represents the bug and then fix the bug and watch the failing test turn green.

Most of the writing I’ve seen around TDD and BDD explains it from a feature perspective first, but if you look at it from a bugs perspective you can easily reframe features as debugging them into existence. Once I started thinking about it like this, it makes a lot of sense why I got so hooked. Being someone that enjoys and is pretty good at debugging, it is obvious that a process which turns programming into a constant debugging session would click. It is a bug that the feature doesn’t exist, so you need to write a failing test that exposes that bug.

Optimize for Debugging Speed

“If debugging is the process of removing software bugs, then programming must be the process of putting them in. ” – Edsger Dijkstra

As close as you can get to a guarantee in programming is that if you’re building a program of any kind of complexity, you’re going to be spending a lot of time debugging it. Taking Eagleson’s Law* into account, the situation is even more dire – you’re almost always debugging code that might as well have been written by someone else. How would you prefer past-you to have written code so that current-you has an easy time fixing anything that comes up? Besides writing tests, of course, you would prefer that they wrote the code to make the process of debugging as easy and fast as possible.

“ Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it. ” – Brian Kernighan

The code needs to be simple and easy to understand. You can get some of the way there with tools like Code Climate, but the simplicity I’m talking about here goes beyond what static analysis can really quantify. Not only do you need to worry about the low level details of your methods and classes, but you also need to worry about your overall design. Imagine explaining the design to someone new on your team. If the explanation involves layers upon layers of  new classes, while not obviously wrong, you should probably get up and walk around a little bit asking yourself if there is absolutely no other way to accomplish what you’re trying to accomplish.

Beyond a simple overall design, think about each line you write from the perspective of someone unfamiliar with the code reading it for the first time. Consider the `try` method in rails. Any time someone encounters it being used, they now have to incorporate the thought that the caller could be `nil` and have to think about what cases that could be true. Unfortunately, the `try` method doesn’t provide any of the information needed to determine when and whether the object is legitimately null. You should write code that clearly explains to the reader why it is doing what it is doing.

Debugging time increases as a square of the program’s size.
— Chris Wenham in Physics for Programmers

To optimize for debugging speed, after taking simplicity and readability into account, you need to write as little code as possible to accomplish your task. Steve Yegge argues that size is Code’s Worst Enemy, but it is also debugging’s worst enemy. There is plenty written about brevity being the first thing to optimize for, and from a perspective of making the code as easily debugged as possible you absolutely need to focus on expressing ideas in as little code as reasonably possible.

You’re always going run into cases where, like Jeff Atwood mentions above, you’ve completely exhausted all your other options and have to write some code under duress. In these cases, just writing a small amount of code isn’t enough, there is something much more dangerous to watch our for. You need to think about the other code that people are going to need to write to interact with yours, and minimize future-code as much as possible too. Not only do you need to fight the urge to not bring new lines of code into the world, but you need to make it easy for the users of your code to do the same. If you’ve just written a small readable class that now requires all its users to write hundreds of lines of setup just to use it, you’ve still failed. You are now responsible for bringing an immeasurable number of lines of code into the world over the lifetime of the project.


* Eagleson's Law: Any code of your own that you haven't looked at for six or more months might as well have been written by someone else.