Technical Debt Isn’t Real
The origin of this nonsense
“Technical debt” was first used in 1992 by Ward Cunningham to justify an iterative, experiment-based approach to software development. His manager didn’t understand why the team was going to back to work on code that had already been written. Cunningham had to explain that the first code had been put out based on an immature understanding of the problem, allowing for testing and the development of a more mature understanding — which then had to replace the previous solution.
As originally used, technical debt is actually a powerful concept. Forgetting the nature of your experiments and leaving patchwork solutions in place permanently is a recipe for disaster.
Today, however, technical debt is bullshit. Very few engineers would give a definition of technical debt that aligns with Cunningham’s original meaning. According to Wikipedia, technical debt is “a concept in software development that reflects the implied cost of additional rework caused by choosing an easy (limited) solution now instead of using a better approach that would take longer.” Cunningham himself addresses this co-opting of his concept:
A lot of bloggers at least have explained the debt metaphor and confused it, I think, with the idea that you could write code poorly with the intention of doing a good job later and thinking that that was the primary source of debt.
I’m never in favor of writing code poorly, but I am in favor of writing code to reflect your current understanding of a problem even if that understanding is partial.
The tech field at large took “implement a partial solution then iterate” and somehow translated that to “we’re paying a high price for a bunch of poorly-written code”.
Why it’s bullshit
Of course, the original definition of a term doesn’t matter all that much in comparison to its real-world modern impact. And in the case of technical debt, that impact has been monstrously negative.
Today, technical debt has become a catch-all term for any code that a software engineer deems to be less than ideal. It could refer to software that runs slowly, doesn’t have comments, lacks any clear style standards, or straight up doesn’t do what the user needs it to. Essentially, “I need to work on technical debt” has come to mean “I need to make this code less bad”. While that’s not a wrong thing to say on its own, it’s also kind of pointless. Why is the code bad? What will improving it do to help the business?
Because it’s so non-specific, technical debt has become an obfuscator — it hides important meaning that could be sussed out through the use of more specific language. It disconnects resource investment from the generation of business value. And that disconnect really matters. I’m not just being pedantic; software engineers hiding (or not understanding) the reasons for things to be prioritized is a massive source of waste.
In fact, I would argue that “technical debt” is one of the greatest sources of unnecessary cost for most software companies.
Average engineers spend about 40% of their time on “paying” technical debt. What on Earth is that time really being used for?
When “technical debt” actually causes more debt
The biggest problem with the way the industry uses the term technical debt is that it actually hides a whole bunch of bad practices that are legitimately costing companies real time and money. Bad code is being written under the banner of “it’s okay to have some technical debt, we’ll come back and fix it up later”. Like Cunningham has been saying all this time, it’s never okay to write bad code.
So what is bad code that ends up with the appellation of “technical debt”? It includes non-performant (slow) code, uncommented code, bad variable naming, a lack of stylistic standards, and a variety of other no-regrets best practices that virtually all code should follow. Writing code according to these standards takes no extra time or effort, so why would you not do it? The only plausible reason is a lack of experience or skill — which while understandable, really isn’t a very good reason at all for bad code making it into production.
Define your standards, follow them, implement process to ensure them, and provide training and mentorship to those who need it. Don’t be lazy. Don’t call it technical debt.
Admittedly, there is a more thorny class that gets lumped into technical debt: code that was once good but is now bad. This includes code written for a specific use that should now be made extensible or generally any code written for a specific purpose that has been adapted to serve more than that original scope. While no individual decision along the path was bad, you now have code that is unintuitive, selectively non-performant, or nested with veritable fractals of conditionals.
The reason I reject “technical debt” as a descriptor for this type of code is because I believe it is important enough to warrant its own examination on a case-by-case basis. If the code was really bothersome enough to have not been rewritten along the way, then what cause is there to do so now? Does the wasted time of future hypothetical engineers really justify the real expense of actual humans today? Does the additional 3/100 of a second in one of the conditionals necessitate a rewrite? Sometimes they absolutely do. Other times they don’t. Don’t do your organization a disservice by hiding away that analysis in “technical debt time”.
When “technical debt” leads to bad investment
Of course, technical debt’s obfuscation doesn’t stop at covering up legitimate issues. It also leads to a monsoon of time and energy spent on fake problems.
I would argue that many of the “problems” that make up technical debt really aren’t problems at all. I like to fall back on the “does the user give a fuck?” test: if I (don’t) fix this “problem”, how will that impact the user? Perhaps our project’s folder structure isn’t perfectly intuitive. But if I spend half a day reorganizing it is there any guarantee that it will actually be more intuitive? Is the saved cost of getting up to speed in the project worth the headache of current devs having to re-learn the structure? Are those imagined efficiency gains really more meaningful to the user than the new feature I could have given them with the same amount of time and effort?
Much of the most problematic technical debt falls into the bucket of “eh……… maybe”. In other words, it’s often just subjective preference on how a project directory is structured. Or when (if ever) it’s okay to use inline CSS. Or to put one or two lines between functions. None of these decisions directly impact the user. They’re unlikely to even indirectly impact the user — most of the “efficiency gain” is in your head (unless you have well-conducted experiments showing otherwise, in which case please do carry on).
When “technical debt” leads to bad culture
One of the things I’ve found with the “maybe” technical debt problems is that they’re not even universal perspectives on most teams. Maybe the senior engineer really likes comments before a function but several more junior members were taught to stick their comments after the function declaration. In most such situations, the senior engineer establishes “comments first” as a universal truth and any alternatives that sneak into the code base become technical debt. If those alternatives become prolific enough, the senior engineer may go so far as to declare a technical debt day in which a junior engineer is tasked with fixing all of the inconsistencies. All else equal, should the code have been consistent? Sure. But did the user give enough fucks to justify using an engineer’s day on it?
And I think that’s one of the most nefarious problems with technical debt: it enables and masks neuroses and gatekeeping. And in an environment defined by a dearth of technical talent and the struggle to retain what you do manage to attract, I think it’s critically important for us to all do what we can to lower the (artificial) barriers to entry in our field.
Let’s identify the ways that customers are impacted by good code. Let’s figure out what bad code is and teach our junior engineers how to not write it. Let’s establish meaningful standards and adhere to them, providing coaching to others when they violate them. And let’s have the humility to recognize that just because we haven’t fallen passionately in love with how our colleagues write their code doesn’t mean it’s objectively worse or worth taking time to rewrite.