Most of us who work with code have worked with code that slowed us down. Many of us have worked with code that was so bad it made us want to give up on maintaining the code at all. Some of us, to our shame, even wrote the stuff.
Many of us have also worked with code that was easy to change, that seemed to want to change to do whatever was needed. And to our shame, over time, we may have converted this lovely malleable code into stiff unresponsive unmaintainable code.
Looking back at code that is hard to change, and comparing it with code that is easy to change, we can usually see that “Mistakes Were Made”. These mistakes accumulate. The modern phrase for that accumulation of evil is “Technical Debt”. What causes technical debt and what can we do about it?
Technical Debt == Bad Design
There it is, bluntly and simply. Technical debt is bad design. Good design is modular: It has each discernible bit of design in a single place, not spread all over. Good design is not repetitive: each design element is used repeatedly, not copied and bashed into each new application of the idea. Good design is expressive: if a useful design idea was in your head, it is visible in the code. Good design is lean: it does not unnecessarily multiply elements.
Readers will recognize here a rephrasing of Kent Beck’s description of simple code:
- Runs all the tests;
- Contains no duplication;
- Expresses all the design ideas;
- Minimizes the number of program elements.
What we want is code with every idea in its place and a place for every idea. What we have in a situation of technical debt is … not that. We all know how to recognize it when we see it — or when we finally see it — because we don’t always notice the trouble before it is too late.
How can we recognize technical debt sooner? What should we do to prevent it? What can we do when, after all our best efforts, we wake up one day surrounded by the stench of rotting festering code?
Preventing Technical Debt
Let’s Not Push So Darn Hard
When we push to go fast, we miss things. We may even notice them, but we’re too busy right now to change them. Maybe we make a note, maybe we just let it slide. At the end of the session, the world is just a bit dirtier than it was when we started.
Let’s Play Our Best Game, Always
Everyone surely has a set of personal practices that leave code better — and practices that let it deteriorate. In principle, we can always use those practices. In practice, we may not always know how, and we may just let things slide for one reason or another. Personally, I believe that development always goes faster when I work in “full quality” mode, even over the very short term.
Let’s Always Be Ready To Stop
At the end of the day or the session, we may feel pressure to stop. We may decide to leave the code just a bit smelly, thinking will clean it up next time. Yeah, right. That’s why my desk looks the way it does.
Speaking of my desk, I’m now to the point where a major refactoring seems like the only way out. I’ll think about that later but let’s imagine that I get it clean, somehow, magically. (I could do my desk by sweeping everything into a nice plastic box. I could dig into the box if I ever need anything from the desk. But for now, imagine I get it clean.)
What if, every single time, when I use something or receive something, I put it where it belongs? Every single time. My desk would be clean, every single time. There would be at most one thing on it, still in process … uh oh! Watch out! When I come back, maybe that won’t be the thing I work on. Then there will be two things. No, no, stop me. Every single time, even if I’m going to come back to it, what if I put the thing away.
Wow, clean desk! Can I do it with my desk? Can I do it with my code? Maybe not. But can I improve my habit of being always ready to put my work, and my code, away? If so, it would surely help.
These Tricks Never Work
OK, Captain Morality. All these thoughts on keeping things clean are great. But I happen to know that you aren’t always able to do it, and there’s no one riding your back to do another feature. It’s just not realistic to imagine that people are going to have enough discipline, always, to keep the Technical Debt Kneecapper away from their door.
It seems like there is going to be debt even with fresh code written with eyes open. Besides, a lot of us came in in the middle of this movie and there is already debt built up. Do we live with it, or do we pay it back? What’s our best strategy?
Repaying Technical Debt
There are a few ways of recovering from excess technical debt. Along some spectrum of hugeness, we might see rewrites, major refactorings, and incremental cleanups.
Most of us have seen a program that was such a mess that the only thing to do seems to be to start over. In my opinion if the existing program works at all, this is almost always a bad idea. It will be a long time before the new version is good enough to replace the old, and all that time someone will be paying for us to program, but receiving no benefit. This tends to make them tired. In addition, it’s really no fun slogging away like this.
When rewriting the whole thing isn’t acceptable, sometimes people want to undertake a “major refactoring”. They want to spend a bunch of time cleaning up the code, then to return to delivering features. While this isn’t as bad an idea as rewriting it all, it smacks of the same thinking and has the same drawbacks. Someone is spending money and getting no benefit.
In addition, it’s awfully easy to dive into some refactoring and never come up: “Our refactoring failed.” This is especially true if we don’t have lots of solid tests. And we haven’t, have we?
Not recommended. If you must, try Strangler.
The approach here is that when we are doing a story in some area of the code, we clean up that area, using some amount of time that is proportional to the time it will take to do the story. Maybe if the story takes a half day, we spend another half day cleaning up. In this way, we continuously offer stories, and we improve the code in areas that are more likely to be visited again, because they are being visited now.
We don’t rewrite the area; we don’t spend days in there for a two-hour job. That’s disproportionate in my opinion. We just make the place noticeably better than it was when we came in.
What if every time I came into my office area I cleaned up one item, or as many books as I could grab in my hands? Might I not make progress? I think I would. Hmm … I’ll try it.
If (and it’s a big If) we really need to replace some system, consider using a strangler kind of approach. The basic idea is to write new bits of the application, and put them in place so that they replace the old equivalent bits. Over time, all the important bits wind up being in new code.
At this moment, I don’t have much more of a “position” than this:
- Technical debt seems like a good metaphor for the cruft that builds up over time;
- The best thing is to avoid it as much and as long as we can;
- Avoiding technical debt requires a level of discipline that is hard to sustain;
- Repaying technical debt is best done incrementally — any other approach is, in my opinion, usually suicidal.
I’d like to know more. I plan to think and listen until I do.