Technical debt is a ubiquitous problem for engineering managers, but what does it actually mean and how can you best attack it while balancing the other needs of the business?
In the financial world, debt is a loaded concept. Running up your credit cards recklessly is a recipe for disaster. But good financial planning also includes leveraging the right kind of debt. Most of us could never buy a house if we couldn’t take out a mortgage.
In the 1990s, legendary software developer Ward Cunningham – inventor of the wiki and co-author of the Manifesto for Agile Software Development – proposed the idea that writing and delivering software is like going into debt.
Most developers have had the experience of rolling out production code that they know contains flaws that will result in more work in the future. Cunningham sees this as an acceptable practice in order to gain market share or meet important deadlines, as long as it is accounted for in long-term planning.
“A little debt speeds development, so long as it is paid back promptly with refactoring,” he said, back in 1992. “The danger occurs when the debt is not repaid. Every minute spent on code that is not quite right for the programming task of the moment counts as interest on that debt. Entire engineering organizations can be brought to a standstill under the debt load of an unfactored implementation.”
This idea of technical debt has become a crucial one for engineering leaders. Sometimes the phrase is just used as a way to complain about old code that was rushed into production and that nobody has had time to fix. But a smart leader can use the idea to quantify the tradeoffs between development speed and code quality.
What is technical debt?
The most useful way to think about tech debt is that it represents work you’ll have to do in the future to refactor or update code that you’re using or releasing to production today. If your current application is prone to errors or is difficult for developers to work with, this extra toil represents the ‘interest’ you are paying on technical debt incurred in the past.
While that should work as a definition, people often mean different things when they talk about technical debt. Mozilla engineer Chelsea Troy says that the phrase has become so ubiquitous that people will often “slap the term onto whatever happens to bother or frighten them” when discussing code they don’t like. You’ll want to listen closely to, or interrogate what people are really talking about when they use the phrase, and use the term yourself with caution.
Why is technical debt important?
Is technical debt necessarily bad? Well, sometimes it can help you get to market faster or roll out a minimal viable product. In those instances, it can be a good thing, or at least a necessary evil.
As an engineering leader, your team will be subject to multiple contradictory demands. Delivering the highest quality code on the fastest possible timeline is impossible. As a concept, technical debt is important for managers because it provides a framework for decision-making when you’re under a time crunch. Documenting and quantifying the shortcuts you take today in the race for a minimal viable product can help you plan to refactor and improve the codebase going forward – or can at least help you realize that some work simply can’t be postponed.
The concept of technical debt can also help you manage when you’re dealing with a chaotic codebase that’s already in production but needs improving. Determining how much time you waste dealing with a subpar infrastructure – the interest you’re paying on the debt incurred long ago – can help you justify effort today to refactor code and move into a technical debt-free future.
Types of technical debt: planned and unplanned
Tech debt, then, can fall into two broad categories: planned and unplanned. In an influential essay, Martin Fowler embraced this dichotomy and also suggested another one: a distinction between reckless and prudent debt.
Prudent debt is accounted for when taken on, whereas reckless debt is piled up without much thought for the consequences. This gives rise to what he calls the technical debt quadrant:
- Deliberate/reckless: “We don’t have time to properly design this, so we won’t.”
- Deliberate/prudent: “We’ve assessed the risks of this design and are going to roll it out, knowing the consequences we'll have to deal with down the line.”
- Inadvertent/reckless: “Everything will probably go fine!” [Things end up not going fine]
- Inadvertent/prudent: “We missed some problems during development but we’re keeping on top of them as they arise and learning from our mistakes.”
Needless to say, as an engineering leader, you should push your teams to be prudent rather than reckless when taking on technical debt, and to do things deliberately rather than inadvertently.
To this dichotomy, we might add another type: environmental debt, sometimes called bitrot. This represents the slow degradation of a system that is in use for many years, with complexity and errors building by slow, incremental changes, sometimes implemented by people who weren’t involved in the initial rollout. This is a form of inadvertent debt, but it arises more via entropy rather than something that can be traced back to a bad decision. Still, it produces negative consequences and toil, and is best dealt with like other types of debt.
What are the causes of technical debt?
So far we’ve been talking in somewhat abstract terms. Let’s dive in and discuss some more specific scenarios that could result in your team dealing with technical debt when they’d rather be coming up with new features or applications.
- Time pressure: This is probably the most common reason why teams take on technical debt. Your job as an engineering leader is to help prioritize tasks and keep track of work that’s being postponed in the quest to meet a short deadline.
- Ambiguous project requirements: If it’s not clear what your team’s ultimate goals are, that increases the chances that they’ll release code that needs to be reworked or completely rewritten later. Team leaders need to work with all stakeholders to make project requirements as clear as possible from the beginning to avoid later toil.
- Bad code: A basic and obvious problem that can have endlessly irritating repercussions. Well-written code is easier to implement, work with later, and integrate with new modules. Bad code will slow down later development and eat up time when it eventually needs to be rewritten.
- Poor tool choices: As an engineering leader, you need to make sure that the tools chosen for the development environment are the best available and are right for the business case; otherwise, they’ll result in a waste of resources in the long term and will ultimately need to be replaced.
- Bad documentation: Don’t underestimate how much time and effort will be wasted down the line if you don’t properly document your code when you write it. Make sure your team has processes in place to document modules as they’re written.
- Lack of testing: Test-driven development ought to be table stakes in a modern environment. Code that isn’t thoroughly tested when it’s rolled out will almost certainly include errors and will require effort to fix later.
- Change over time: Even the best-planned and best-implemented codebases require more and more maintenance and toil over time. Business needs drive requests for new features; changes in the cybersecurity landscape require patches and revisions; programming languages, code libraries, and development frameworks become outdated. As an engineering leader, you’ll need to recognize when the toil of dealing with debt that slowly accumulates in this way eats up so much time that an extensive application rewrite is called for.
How to manage and reduce your technical debt
Whether you’ve taken on technical debt for good reasons or bad ones – or if you’re left to deal with debt already accrued – part of your job as an engineering leader is to help manage and reduce that debt so your team can focus more energy on building new features and new applications.
One of the defining features of tech debt is that it absorbs developer time and effort that should be dedicated to more productive tasks. Thus, it may seem counterintuitive that truly getting a handle on the problem can involve spending more time and effort on tech debt, at least in the short term. Think of it as immediate pain for long-term gain, just as cutting back spending for a few months to pay off your credit card will give you more room in your budget for discretionary spending in the future.
Here are some practices that you as an engineering leader can roll out to help manage and reduce technical debt:
- Get your team on board. Many engineers who are unfamiliar with tech debt (or who just use the phrase to mean “something that annoys me”) might think of the toil tech debt causes as just a part of life. You need to show your team how debt can help in the short term but require attention in the long term in order to make their lives easier.
- Maintain an inventory of your debt. Are you choosing to take shortcuts during a product or feature rollout? Document what you’re doing (or not doing) and sketch out what needs to be done to refactor things properly, and when you think that needs to happen. Are your engineers banging their heads against the same poorly written code over and over again? Have them figure out the parameters of the problem and try to get a sense of potential solutions rather than just shaking their head and grumbling.
- Make resolving debt a first-class part of your workflow. Once you know what you’re up against in terms of tech debt, you need to build the process of resolving it into your team’s daily life. That means adding tasks related to resolving debt into your backlog and setting specific deadlines for completing them, rather than just leaving them for when team members have extra time. You also want to make sure that your developers know that completing these tasks is just as important as anything else on their list by rewarding them accordingly.
How to explain tech debt to non-technical stakeholders
We’re not here to debate how well the metaphor of financial debt applies to what we call tech debt, but it definitely has one advantage: non-technical executives and decision-makers, who often focus on the financial aspects of running a business, can quickly grasp what you mean by it. That’s important because asking for time and resources to improve code that’s already in production can be a tough sell.
When discussing tech debt with executives, it helps to come equipped with numbers. How much time is your team wasting on toil that could be resolved with some focused effort? What are the impacts of a suboptimally performing application on customer service or sales? What sort of advantage can you gain by getting an application or feature rolled out quickly, even if it means work to refactor it further down the road? The answers to these questions can buy your team the time and space they need to keep on top of technical debt.
How to calculate your technical debt
Delivering those numbers is easier said than done, but there are a number of techniques and metrics you can use to do so. One of the most prominent is the maintenance load – that is, how much effort your team needs to put in just to keep an application working with the features it already has. The higher the maintenance load, the less time and energy team members have to roll out new features or improvements.
There are a number of other metrics you can use to assess your current debt load. These range from the relatively simple, such as a ratio of resolved to unresolved issues, to more complex and subjective metrics like the Technical Debt Ratio (TDR), which can help price out future debt by comparing the cost of writing code to the cost of remediating in the future.
Whatever techniques you use to calculate your current and future debt, know that just acknowledging that debt puts you into the “deliberate” half of the technical debt quadrant, which is exactly where you want to start.