The term technical debt is so common that you’d be hard-pressed to find anyone in the software world that hasn’t heard of it. But what is technical debt? There are a few frameworks out there, so I’ll list them quickly, but then I’d like to present one I’ve found especially useful with the teams I work on or advise: inertia vs. interrupts.
First, it’s helpful to define what technical debt is not. Bob Martin has a great article on this called A Mess is not a Technical Debt. “A mess is just a mess”. You can make short-term decisions that may not be best for the long-term, if you’re under constraints, but you should still do it in a prudent way. Some decisions are just bad.
Martin Fowler expands on this by creating a Technical Debt Quadrant, with two dimensions: deliberate vs. inadvertent, and reckless vs. prudent.
Ideally, you’re in the right half of this two-by-two: always prudent, because there’s no excuse for being reckless. But, if you’re low on experience for the type of system you’re designing, you might be prudent and inadvertent. Martin Fowler argues that prudent / inadvertent is really common with great designers, because they are always learning.
One of my favorite frameworks is from the Mythical Man-Month’s No Silver Bullet essay. Fred Brooks breaks down complexity (which is a little different than technical debt) into to dimensions: accidental complexity and essential complexity. Essential complexity is caused by the complexity of the underlying problem that software solves—accidental complexity is introduced by the implementation developers take. In this world, technical debt is, essentially all accidental complexity.
A final framework defines technical debt as the gap between how software is structured and how you would structure it if you were writing it from scratch today (I don’t remember where I read this definition, please tell me if you do!). In this world, technical debt is a little more fluid, because it can increase simply by your team thinking up a better architecture or design.
A different framework—manifestations
These frameworks are all great, and in fact, you can go even deeper to define what technical debt looks like. Books like Refactoring and Clean Code have done this well. But usually, what you need, is something a little more concrete that you can make decisions about.
So the framework I like to use is to look at manifestations of technical debt; what impact does technical debt have? By looking at how technical debt actually impacts your ability to deliver product, you can make decisions in a less subjective way.
At a high-level, technical debt can manifest itself in two ways:
- Interrupts: Interrupts are when existing systems are taxing your time through reactive work like maintenance, bugs, fire-fighting, etc, so you spend less time being able to change a system. In other words, writing code now that creates interrupts in the future means you (or someone else) will be able to spend less time on forward work in the future. Both the quantity and severity of interrupts matter. Interrupts are particularly hazardous because they usually result in costly forced context switching. So you find your team, over time, spending more and more time responding to incidents than building product.
- Inertia: Inertia means that a system is hard to make forward changes to (because it is hard to understand or reason about, because it’s not modular and hence hard to change, etc). This makes forward work difficult for you (ie even when you can spend time doing forward work, it’s really slow) or for others (e.g. because the system is hard to understand, it is a tax on the time of people who need to learn more about it, as well as on people who need to explain to others how it works).
Why look at the manifestations? Two reasons.
First, it helps identify the tangible symptoms and effect of technical debt, and helps avoid theoreticals. For instance, most teams have at least one part of their code base that they feel pretty bad about, and would love to spend time fixing. Is it worth fixing now? Sometimes, that code is well-isolated and pretty functional. It isn’t creating any interrupts. No one is changing it or will need to change it in the near future. So yes, it is technical debt, and it might have inertia, but inertia only matters if that code needs to be changed.
Secondly, by identifying the effects of technical debt, you can decide how best to fix it. For instance, if a piece of code is really hard to change because it suffers from change amplification, and you expect to be making a lot of changes to it, it’s probably worth refactoring. If a piece of code is creating a lot of interrupts and not creating value for users, you might want to just get rid of it entirely.
Finally, it helps avoid ideological discussions. We’ve all worked with someone who is ideological about their code. “X is shit, Y is great”. By looking at manifestations, you’re forced to be thoughtful and justify any claim you make. “X is shit” doesn’t fly any more. You have to say “X is bad, because when we want to do Z in the future, it will be really difficult.”
So sometimes, it’s useful to start with the symptoms before the diagnoses, and look at how technical debt manifests itself before deciding what to do about it.