Technical debt isn't technically debt
The term "technical debt" has entered the standard lexicon of programming and software project development and has often been called out for being an incorrect metaphor. Yet it persists as a way to talk about the decisions made during software engineering. I believe it is over-used and often makes technical conversations more confusing.
Our team at Aha! has meaningful conversations about the trade-offs of software development without using the term "technical debt."
There are two common ways the term "technical debt" is used now. The first is when engineers are talking with someone who doesn't have the same technical context of a project. In this case, it may be used to smooth over the difficulty of communicating various ideas. Those ideas are typically:
- We can deliver feature 1 faster but that means feature 2 will take longer.
- This feature is going to take longer because of how we approached earlier features. You can see that those are conceptually similar other than the timing. The message is that the developer needs more time to work on these features. Calling it technical debt avoids the need to explain the finer details. You might also say:
- We’ve incurred technical debt so we need to clean that up before planning the next features.
This communicates that engineering is not happy with the code as it stands. We would like to work on it without needing to worry about delivering functional changes. The term is again used to avoid further explanation and scrutiny. It has entered the mindset of many people in software development that "technical debt" is something that just happens and needs repaying.
The other common usage is between engineers as justification for making certain decisions or for why the code is the way it is. Many times it sounds like an excuse. If an engineer is embarrassed about the state of the code or architecture, they may call it technical debt to signal to other engineers that the problems arose due to external factors.
I’ll address the problems with these usages, but first let’s consider what technical debt should actually mean.
What is technical debt
Let's remove all the built-up subjective meaning that has crept into the term "technical debt." Similar to monetary debt, it is a balance between having what you need now and the total costs plus risk. That balance plays on our tendency to want results right now and to play down the long-term consequences. Consider the technical debt thought process:
You need to deliver a feature.
- Option A: Deliver the current feature as fast as possible. Your subsequent features may take longer due to more necessary patchwork.
- Option B: Take time to build a maintainable solution for the current feature. Your subsequent features may require less heavy lifting.
Which do you choose?
Not technically debt
I believe "debt" is simply the wrong way to describe this process. With monetary debt, your choice is clear and defensible: I need this money right now or I don’t. Ideally, the loan terms and outcomes are presented upfront.
This technical choice is not so cut and dry. Is Option B better? How can we be sure that investing extra time now will pay off later? For financial assets, calculating risk is a well-established process. Software development isn't as predictable. Future product and business decisions may alter your plans altogether. A shifting roadmap may waste your big investment.
Feasibility also plays a part. Software is built in layers — some of which we don't control. Architectural concepts emerge over time and change to meet the needs of the system. Is this choice apparent and real? Is the engineer working on the problem in a position to choose? I’ve been down enough dead-ends and thought experiments to say that often an apparent choice is a false one. Those experiments might inform later changes but that does not make the current implementation a debt.
Managing uncertainty
Option A may slow down your subsequent features, but it may not. You may have truly found a faster solution. Option B may save you hassle in the long run, or it may not. Your time spent upfront may be wasted if the roadmap changes drastically.
With all of the issues around estimating software projects, it is impossible to predict with certainty. The next feature might not be developed, the requirements might change. Traditional debt has fewer surprises. You don’t take out a loan on a car, discover it was a boat and find out you actually need to take it skiing.
Consider this choice instead:
You have one feature that you will deliver first and one follow-up feature.
- Option A: Deliver the first feature faster with non-ideal code or technical choices to make the second feature easier.
- Option B: Take longer to deliver the first feature and perform less rework for the second.
With either option, you haven't created a debt. You've simply made decisions about the timeline. This is called planning and all good plans take some finessing.
When relaying updates to cross-functional teammates, simply explain the problems and give realistic timelines based on what needs to happen. Blaming a delay on technical debt only hinders transparency and keeps your teammates in the dark.
With your development team, technical debt should not be an excuse to hide behind. Decisions are made, mistakes happen, external forces are at work. Discuss what happened and determine your next steps. Your project cannot be bankrupt by technical debt.
Investing time
Rarely do we have all the time we need to make our code perfect. But that's okay. You may have heard that "Premature optimization is the root of all evil in programming," from Donald E Knuth. Certain ideas about code and software are worth tempering — such as making code flexible or thinking about inflection points. But too much can lead to premature optimizations, over-abstractions, and needless configuration. The code itself is easy to change. If you don’t need a reusable abstraction now, add one later.
You've probably heard the saying, "Don't Repeat Yourself (DRY)" — and how applying that too early or aggressively can also introduce friction. Remember these rules of two and three.
For small code, use the rule of three:
- I’m implementing this for the third time — I should introduce a generic way to handle this pattern.
Why three? Because you copied and pasted the first solution and made changes. On the third go, you understand the changes and are ready to solve it differently.
For larger concepts, use the rule of two:
- I made this model and it worked well, but now I want to use it again and it doesn’t quite fit. I should restructure this to be useful.
You’ll want to reuse the concepts that drive other parts of the codebase. They might not be in a good state for reuse, so now is the time to analyze and create layers and abstractions.
The fact that you didn’t do that before is not technical debt. This is software development.
We work fast at Aha! and we use our products to build our products. Our team is happy, productive, and hiring — join us!