I'm giving a talk on TDD at our local UG tonight, and under the influence of some recent posts here on devlicio.us, I just finished reworking my presentation. This post is an outline for the first half of my presentation.
Code has entropy. That is over time it deteriorates. At least, that's the metaphor we developers like to use. What is really means is that the code becomes harder to maintain and extend as it is maintained and extended over time. Did you follow that? Our typical approach to maintaining and extending code causes the code to become fragile and rigid and thus harder to maintain and extend in the future.
There are three common difficulties that arise, maybe more, but I'm focusing on three:
- Unchangeable Code
- Unintelligible Code
- Unnecessary Code
A portion of code becomes unchangeable when its deeply entwined and entangled with other portions of code that have different responsibilities. You can't change method X because we really don't know what that will do to Y and Z.
Code can become unintelligible for lots of reasons: abbreviated names, circuitous logic, enormous methods, confusion of responsibilities, use of php☺, etc.
Finally, unnecessary code is code that is not used by the system. It can be vestigial, or as often happens, it can be the result of feature anticipation. We thought this was going to be needed in the app, so...
There are some generally accepted solutions to these three problems.
In the case of unchangeable code, we can apply such patterns as Separation of Concerns (SoC) and the Single Responsibility Principle (SRP). For addressing unintelligible code, we have a number of techniques such as thoughtful comments, explicit naming, SRP again, etc. Finally, for unnecessary code we have the old axiom of You Ain't Gonna Need It.
Okay, so we know how to solve the problems. Or least, we know how to mediate them. So what? What does that have to do with TDD?
My proposition is this: solutions are without value unless they are applied.
Additionally, when a problem occurs over time, the solution must be applied over time.
I just started going to the gym. It's been about a decade since I did that, but I'm back. For the past several years however, I have been preaching to certain family members the value of exercise and healthy diet. I knew the benefits: stress reduction, longevity, improved sleeping, and just plain feeling better. However, I wasn't really doing either.
I had the problem and I believed in the solution and, in fact, I made attempts to employ the solution. Ultimately, I was unsuccessful. That is, until I committed to go to the gym three days week at a set time and with a workout buddy.
My point is this, we are human beings and despite being smart we frequently revert to doing "the easiest thing". Therefore, we ought to set up some sort of framework of methodology or discipline where the easiest thing is actually what we want to do.
Additionally, I have to keep on going to gym. For as long as I want to be fit, I will need to exercise. In the same way, for as long as I want my code to fit I will need to apply good principles.
TDD as a Discipline
I will argue that TDD is a discipline that enables the application of the solutions outlined above. Perhaps you apply those solution naturally. Perhaps you can benchmark 200lbs because of your genetic disposition. Bully for you. Go ahead and skip the rest.
Let's examine a few essentials of TDD and see if they really address the problems we outlined.
I'm an advocate of automated testing in general. However, I do think that writing your tests first yields additional value. Primarily, it helps you discover your ideal API. In other words, you can design your API up front such that it is more intuitive. More intuitive means easier to understand.
Secondly, testing first helps you identify dependencies early. You quickly discover that class X will need a Y and so you are forced to create seams in the application at the onset. This encourages SoC and SRP.
It took me a long time to understand the "unit" part of unit testing. The idea is that you decompose your problem into units, and deal with the problem one unit at a time. You could call this "test just one thing". Okay, well what is "one thing"? That is hard to tell and comes with experience. Nevertheless, the practice of trying to decompose a problem and identifying units leads you back to SoC and SRP again.
Just Enough Code
Another tenet of TDD is to write only enough code to pass the test. Our natural instinct is to anticipate. It is part of what we are as developer, and it is not a bad thing. However, like many strengths, it can become a weakness. By constraining ourselves to a single problem at a time we will end up with less code. It also forces us to be conscious about our anticipation and to ask if a given scenario is really necessary.
I know that there is much contention over the actual value of TDD as a practice, and I am not going to force it on anyone (unless I hire you and now you are warned). However, I have found it to be a useful discipline for helping me to overcome common problems and because of that I encourage it's use.
11-06-2008 3:03 PM