The Definitive Guide to Technical Debt đŚ
How to prevent it, measure it, and eventually pay it back. Backed by deep research and tens of stories from the community.
Hybrid Hacker and Refactoring together have ~130K subscribers today, who include people with very different jobs: engineers, managers, CTOs, founders, and more.
So, whenever we write an article, it is naturally more or less interesting to subsets of these people. But there are also⌠exceptions: topics that everybody understands and have lived through in a way or another.
The mother of all these topics is technical debt đŚ
I have written many times about it already: on the first time, it was to define it, while on the second one it was to keep it under control.
Both articles are more than two years old now. In the meantime, I have learned a lot by speaking with hundreds of managers and engineers â through the newsletter, and especially through the community, which grew from 50 to more than 800 members.
So, today I am publishing a full, thorough guide on how to handle technical debt: how to prevent it, measure it, and ultimately pay it back.
So, here is our agenda for today:
đ What is Technical Debt â definitions first, to get everybody on the same page.
âď¸Â Good vs Bad Debt? â is there even such a thing like good debt?
đ How to Prevent Debt â daily practices to keep code clean and simple
đ¸ How to Repay Debt â what to do when the crap is already there.
đ How to Measure Debt â how to know how bad things are, and how better they could be.
đŁ How to Advocate for Debt â how to convincingly tell others, especially non-engineers.
đŹÂ Community thread â giant thread with 25+ replies about real stories of how tech leaders in the Refactoring community handle tech debt.
Letâs dive in!
đ What is Technical Debt
Ward Cunnigham is one of the signers of the original Agile manifesto, and the inventor of the technical debt metaphor.
Here is the sentence where it is believed that this metaphor was used for the first time:
If we fail to make the program aligned with what we understand to be the proper way to think about our [...] objects, then we are going to continuously stumble into disagreement, and that would slow us down like paying interest on a loan.
Ward describes debt as the natural result of writing code about something we don't have a proper understanding of.
He doesn't talk about poor code â which, he says, accounts for a very minor share of debt â he talks about disagreement between business needs and how the software has been written.
In other words, software is a formal description of some business reality and needs. Whenever such a description doesnât match reality, we get tech debt:
A design that doesnât scale properly fails to take into account the reality of business volume.
A leaky abstraction is like a bad rule that doesnât fit reality anymore, and needs continuous exceptions to keep working.
So, how did we get there? How do we land in disagreement?
âď¸Â Good vs Bad debt
Whenever you find that your software doesnât fit reality, it is useful to ask yourself: when did it happen?
There are two macro answers to this:
From the start â engineering, product, and business simply didnât fully understand one another, and built a (partially) wrong thing.
Over time â reality gradually changed into something else and invalidated some assumptions.
These differences are not clear cut, as some future changes can and should be predicted from the start, but you can still get a sense of where the problem is based on how early and frequent the rework is.
Is it only weeks after release, or months, or years? What does that code churn look like? Is it the first time you are touching it or is it a regular?
At this point you may ask: since reality changes anyway, isnât debt inevitable? Isnât some debt even good when it allows us to go faster, granted we keep it under control?
This question is tricky and the answer goes back to the knowns and unknowns model. When you design software, you have three sets of knowns:
đ˘Â Known knowns â requirements you know and are under control of.
đĄÂ Known unknowns â uncertain items you are at least aware of, like scale, or some future product directions.
đ´Â Unknown unknowns â all the unforeseeable ways things can fail.
You can classify bad, good, and best designs based on how they account for these:
đĽÂ Bad design ⢠doesnât even account for (1). Engineering didnât really understand product, made a set of wrong assumptions, and design was wrong from the start.
đĽ Good design ⢠solves for (1), but doesnât account for (2). E.g. if scale is an unknown, failure means to commit decisively to a solution that is opinionated about it â you either invest a lot on premature (and speculative) scaling, or on something that will never scale and it is hard to undo.
đĽÂ Best design ⢠solves for (1) and accounts for (2). For example, it quickly pushes out a design that doesnât scale but it is easy to scrap later, once you have figured things out.
So, back to good vs bad debt, and debt as disagreement:
Bad debt is unaware disagreement â you didnât account for things that were under your control.
Good debt is delayed disagreement â you shrink the scope of the agreement today, and accept some disagreement later.
This angle is similar to Martin Fowlerâs quadrant (itâs arguably a subset of it).
đ Tech debt vs Product stage
I believe that the opportunity to accept the tradeoff of creating intentional debt depends on your product stage.
Fast growth naturally leads to debt, because when the product changes at a fast rate, your engineering abstractions get invalidated equally fast. Such volatility changes with the maturity of your product.
For the sake of simplicity, letâs consider three stages: