Who's afraid of big bad technical debt?

Every conscientious software engineer knows about technical debt. Lots of us learned the hard and painful way that deferred refactors and sloppy code can and will come back to bite you eventually. Since we don't like being bitten, especially by our own code, we learn to hate that nasty technical debt. All manner of evil gets blamed on it, and we avoid it like the plague. Like some digital bogeyman, we speak its name to scare junior developers into writing good code.

But in the real world, there are real constraints - like time, money and the number of cups of coffee you can safely consume in one day. Software development will always be forced onto the dilemma: Do you want it okay sooner or do you want it better later? The perfectionist will say that you can have it when it's done (perfectly) and he'll let you know when that is. The hotshot will tell you it can be done in an hour, but at least he's honest that it won't work after tomorrow. The idealist wants it all - better code, faster. Keep the idealist around for the projects without real constraints.

Seasoned engineers have learned that technical debt isn't always bad. Just like real debt, it can be dangerous, but it can also be a useful way to trade schedule risk for technical risk. Sometimes, the cost of a missed deadline is greater than the cost of some ugly code, especially in the short term. Professionals consider and compare these costs constantly, over appropriately-chosen time frames. They share their evaluations with the team, and build consensus about the trade-offs. They see the software not just in terms of potential and real debts, but also as potential and real investments.

Since technical debt isn't always evil, and can grow and shrink in order to achieve the schedule and technical goals of the project, then senior engineers and technical managers should know how to manage that technical debt responsibly. There are two key measures that help managers analyze technical debt: budget and discretionary spending.

Tracking your technical budget

For a project that's already accumulated some technical debt (and really, every non-trivial project has some debt) keeping track of your budget is usually as easy as asking one soul-searching question: Is the overall average quality of the code increasing or decreasing? You spend plenty of time reading existing code, so you have a good sense for its quality. You might even have automated complexity metrics running on your code. If you're not confident that what you're adding and revising is significantly better than what’s there, then you're probably making it worse. If that lasts for a while then you can bet that you're running a deficit technical budget, and therefore accumulating more debt. Healthy, mature projects don't run a technical deficit for long durations.

However, for a younger project, that diagnostic question doesn't work as well because 1) the code is fresher, so you don't have the benefit of a longer perspective on it, and 2) you're spending more time writing code than reading so you don't have as good a sense of the quality of what's already there, and 3) there's not that much code anyway, so you tend to compare every new feature against nothing at all, which compares rather favorably, regardless of the quality of what you're adding.

In many ways the planning process is more important for the younger project, because there are more choices to make - choices that will affect the technical budget. The young project’s technical budget is like that of a recent graduate who finally starts bringing home a "real" salary from his first job. How should he spend this new income? Dining out every night? A mortgage on a new house? Longer-term commitments like signing a lease or buying a house will really change how much extra spending money he has.

Technically, decisions like whether to commit to a new feature for the current sprint, or whether to design for a possible future requirement have to account for how much effort is currently budgeted for the project, and how much of that effort is available for new initiatives. If you’ve ever debated whether to spend more time doing fewer features more carefully, or to move faster in order to hit more features sooner, you’ve experienced this decision process.

Technical discretionary spending

Dining out is way better than Ramen noodles, but just because you can eat out every meal doesn't necessarily mean that spending 45% of your income on sushi is good financial management. Financial advisors call this "discretionary spending" because you could live just fine on groceries, so anything more expensive is a choice. It’s wise to figure out what your minimum, non-discretionary technical costs are, that is, the least amount of effort required just to continue building and maintain the application. That means never fixing something that isn’t broken. No unnecessary improvements. If you can live with it the way it is, you do. All the effort above that amount is discretionary, so you have to decide how to spend or invest it.

Younger projects need a different diagnostic question: How much time is spent on discretionary technical work? You know there was a quick and hacky solution that would have worked, and you have a pretty good idea of how long it would have taken you. But because you have some pride in your work, you didn't do just the bare minimum. You spent some time coming up with better names. You did a little refactor. You wrote better commit messages. The time you spent on that stuff was your discretionary technical spending. What percent of your technical work was discretionary? 10%? 50%? 90%?

Barely eking by is as wearisome technically as it is financially. Many of the most satisfying things we make and do come out of the comfort and luxury of discretionary spending. But, on the other hand, the stupidest and most useless things also come from an abundance of discretionary spending. We'd be wise to ask how much of a good thing is still good. How much time should be spent on discretionary technical work and how does that affect a team's velocity?

If the team's discretionary spending is only 5% of their technical effort, then you know the current team can't realistically build any faster than they currently are. On the other hand, if it's 50% then you know you could build things almost twice as fast if you had to. Likewise, if it’s 90% then you know you could be adding new features much faster if you wanted to.

With so much flexibility on a young project how can we choose a good balance of discretionary technical effort vs. faster addition of new features? One important factor we need to consider is the technical net worth of the project.

Calculating your technical net worth

The size of your technical credit limit (or piggy bank) is proportional to the maturity of your software project, both the codebase and the documented architecture: If your project has just started and you don't have much code, then it's not even possible to accumulate significant technical debt. You just don't have that much in collateral assets, so if the debts come due, you can just declare bankruptcy, throw your code away and start over. If you have barely documented the architecture, and you make a fatal design mistake, you can still scrap the design and fix your mistake.

By the same token, with little code and little documented architecture, your current technical assets aren’t adequate vehicles for significant technical investment. You just don't yet have any good place to store many clever technical solutions or to realize the benefit of design refinements. First grow the design and codebase, and then you’ll have something worth maintaining.

On the other hand, if you already have a sizeable codebase, you have lots of capacity, both for crippling amounts of code-debt, but also liberating amounts of investment that will pay dividends. A mature design can’t just be discarded, so it’s worthwhile looking for even small improvements that will keep well matched to the problem at hand. A big codebase should make you cautious about neglecting it, but should also give you confidence that smart refactors have a chance to make a big payoff in terms of speed and ease of future development.

Conclusion

Projects with greater technical assets can afford higher technical discretionary spending. 90% could be reasonable for a large project, especially if it had some significant technical debt. 50% could be required on a large, well-maintained project, just in order to avoid a running a technical deficit. If you haven't accumulated any significant technical assets, then you should minimize your discretionary tech spending and focus instead on building just what's needed. 10% could be perfectly reasonable in that case.

Takeaways

  • Don't fear technical debt - manage it carefully.

  • Always know whether you're running a technical deficit.

  • Know your capacity for technical debt or investment.

  • Consider whether your technical discretionary spending level is
    appropriate for your project's stage of life.

Caveats

  • Yes, what's discretionary is somewhat subjective. One person's double latte is another person's rice and beans.

  • Don't confuse discarded unworkable solutions with discretionary technical spending. Sometimes, coming up with one workable approach requires trying and discarding a dead-end. If your fastest path to a working solution would have gone through some dead-ends, then they weren't discretionary choices. On the other hand, if you knowingly try a riskier approach first and then discard it, then that first try was actually discretionary.

  • Don't think that, just because it doesn't crank out an immediate feature, architecture design and documentation is always discretionary. Likewise, don't think that all architecture design and documentation is non-discretionary. In general, knowing how much up-front architectural design is "enough" is more art than science, best left to somebody who's made the most prior architectural mistakes in their career.