Programmers tend to treat their keystrokes as gold. Perhaps they are driven to counting by project managers. Perhaps their typing skills are atrocious. Regardless, there is a great reluctance to discard code that is wrong. It is wrong because it doesn't work.
A programmer hates being wrong; at least in that respect, a programmer is like every other human being. But in the case of code that doesn't work, there is an almost irrational drive to make it right by adding more code, even if:
- It means abandoning the design,
- Coding practices must be abandoned,
- Complementary changes must be injected into completely irrational areas of system, or
- Changes to key interfaces must be renegotiated.
Traditional phased process (aka waterfall) tacitly accept and encourage these practices. Testing is done by someone else, usually at some point in the future; this lulls the programmer into thinking the code probably works, and it is someone else's job to validate that assumption. Bugs (the real name of code that doesn't work) maybe fixed by someone else. The chances that I, as the original programmer, might have to make it work are reduced.
But then comes Agile, upsetting this comfortable, lazy acceptance of bad code. Two practices in particular inhibit letting the bad code slide: Test-first development and pair programming.
Test-first has the programmer write the test first, then enough code to make sure it works. Then write another test. The effect of this is that code that doesn't work is immediately recognized. Since all the tests are run before check in (or at least run by automatic continuous integration tools), code that injects a bug somewhere else is detected. But what if the programmer skimps on the tests? What if the boundary conditions are ignored so that personal/team/manager schedule expectations are met?
The second practice, pair programming, makes that more difficult. Now two programmers have to conspire to ignore poor test coverage. But that focuses too much on the negative. Rather with a partner sharing the problem, the second pair of eyes helps identify the marginal cases, and helps find alternative solutions. It becomes almost a mutual quest to solve.
The New Problem of Keystrokes
Agile creates an environment where bugs are rapidly detected. So programmers can be lulled into taking bad code and continuing to change it in the hope that it will (magically) start working. In the ThoughtWorks programmer training course, one objective is to get programmers to overcome their reluctance to throw code away and start over.
The first trick to teach programmers to work in small cycles. In the class, we give a problem to the students (always working in pairs). The students are to write one or more tests and get the tests working. And they have only 15 minutes! Invariably, students start with a test that will prove everything works. When the timer goes off at 15 minutes, they are actually stunned that all the time has passed. They are nowhere near getting it to work. They ask for another 15 minutes, certain that they will finish in that time. Invariably, almost all fail again. Sometimes I give them more time, but it is only to get them to realize that their quest is fruitless. Then you finally have pity on them and show them the secret: write smaller tests. Get something working in the 15 minutes. That "resets" the 15 minute clock.
By the second week of the course, pairs are competing on how many cycles they got done in 15 minutes. They discover that in these tiny steps, they are marching to completing the work with absolute confidence that the solution works when they get there. Indeed, they are stunned when they write that ultimate test of the overall behavior, and it takes just a few lines of code to get it to work. A stark contrast to the frustration they felt when biting off too much.
Now the second trick kicks in. If you can't get the code working quickly, throw it away and start over. In these small cycles, they haven't written that much. The old code worked; the clock is ticking; rather than trying to get the bad code to work, they back up and start over. Code repositories are a huge tool in this; modern tools allow you to revert all the code in a couple of mouse clicks. Now the programmers don't treat that bad code as gold.
The top reasons to discard the code are:
- It doesn't work. So why are you spending more time creating a bug?
- If the reason seems to be a muddled design, throw the code away and re-think the design. Kent Beck said, "Code is where design meets the harsh reality of dawn." If the design is bad, the code will scream it out. Listen to the code. So why are you investing more time implementing a bad design?
- The programmers don't understand the problem yet. Getting stuck can be a sign of a lack of understanding. Step back and discuss the problem some more. So why do you think you will understand the problem better by continuing to write code?
See the trend? Continuing to write code is a waste of time. Discard and start over. It is not gold, but rather
If you are a programmer, try this out: If the code seems to be going nowhere, throw it out and start again. Let me know how it goes...