Technical debt: the three signals that justify refactoring, and the ones that justify nothing
Everyone talks about technical debt; very few measure it. The result is that teams refactor what annoys them rather than what costs the business, then wonder why management never funds the work.
Christophe Bellec ·
Short answer
Refactoring is justified when the code slows down a requested change, when it causes repeated incidents, or when it blocks hiring and team autonomy. Code that is ugly, old, or simply not written the way you would write it today justifies nothing on its own: debt sitting in a stable area nobody touches costs nothing. The question is not “is this code good?” but “is this code costing us something measurable?”
Debt is not ugliness
Technical debt is a past decision that makes future changes more expensive. The important word is “future”: if no change is planned in an area, the debt sitting there costs nothing, whatever the code looks like.
That distinction separates refactoring that gets funded from refactoring that never will. “This code is messy” is not an argument. “This area absorbed 40% of our development time last quarter” is.
The three signals that justify acting
Signal 1: The code slows down a requested change
A modest feature demands disproportionate effort, or the estimate explodes because five apparently unrelated places have to be touched. The debt then becomes a cost directly chargeable to the roadmap.
What makes it visible: comparing estimated effort against actual functional scope across several changes. If the gap always concentrates on the same modules, you have your target.
Signal 2: The same incidents keep coming back
One area regularly produces regressions, or a fix breaks something else. Here the debt no longer costs only development time: it costs availability, customer trust and support time.
The reliable signal is repetition. An isolated incident gets fixed; an incident that returns in similar shapes points to a design problem, not a bug.
Signal 3: Knowledge does not circulate
Only one person can evolve a module, a new joiner takes weeks to ship their first change, or the team systematically avoids an area. That is an organisational risk more than a technical one, and it is the one that costs the most the day someone leaves.
The three signals share one property: you can observe them without reading a line of code. That is what makes them usable in a conversation with non-technical management.
Five reasons that are not reasons
- “That’s not how we’d write it today.” Probably true, and irrelevant if nobody touches it.
- “The framework has a new major version.” An upgrade is justified by support, security or a feature you need, not by a version number.
- “There are no tests.” Adding tests to a stable area with no incidents buys nothing. On a critical path modified every month, it is a priority.
- “It’s a monolith.” A well-structured monolith works perfectly well. Splitting into services solves an organisational and deployment problem, not a tidiness problem.
- “The team would rather work with something else.” That is a genuine hiring and motivation question, but it should be handled as such, not disguised as a technical argument.
Measuring without building a machine
Four indicators are enough, and they come from what you already have:
- The build-versus-fix ratio, quarter by quarter. If it drifts, debt is growing faster than you repay it.
- The files that change most often, pulled from the repository history. Those are the ones worth keeping clean, not the ones untouched for two years.
- The files that change together. Strong, invisible coupling shows up directly.
- Lead time from “done” to “in production”. It measures friction across the whole cycle, not just the code.
Cross-referencing points 2 and 3 with incident-prone areas is usually enough to name two or three targets. That is far more actionable than a tool scoring code quality out of a hundred.
How to repay without stopping the roadmap
The big isolated refactor, run in parallel with product development, almost always fails: it diverges, and then merging becomes impossible.
- Refactor where you pass through: whenever a feature touches an indebted area, improve that area at the same time, within a controlled scope.
- Isolate before rewriting: put a stable interface in front of the problem area, then change what sits behind it without the rest noticing.
- Write tests right before modifying, not “some day”: they act as a safety net for the change in hand.
- Set an exit criterion: “this area must no longer appear in the three most-modified modules next quarter”.
- Document the decision: what changed, why, and what was deliberately left alone.
The debt you should keep
Some debt should stay. A stable module with no incidents that nobody modifies can remain as it is indefinitely, even if it is ugly. Making it elegant spends budget for zero benefit and introduces regression risk where there was none.
Explicitly accepting that debt is an architectural act. It should be written down, with the reason for leaving it and the signal that would put the question back on the table. Otherwise it will be re-debated every quarter.
Frequently asked questions
How do you convince management to fund refactoring?
By translating debt into measured effects: development time absorbed by one area, repeated incidents, lead time to production, dependency on a single person. An argument phrased in terms of code quality almost never gets funded; an argument phrased in cost and risk often does.
Should you refactor before or after a new feature?
Just before, and strictly within the scope concerned. Stabilise the area you are about to modify, ship the feature, stop there. Refactoring “later” never happens; refactoring “while we’re at it” has no boundary.
Is a full rewrite ever the right answer?
Rarely, but yes: when the technology is no longer supported, when nobody left knows how to evolve the system, or when the business model has fundamentally changed. It then has to be owned as a project in its own right, not presented as refactoring.
What is the right repayment pace?
There is no universal percentage. The useful marker is the build-versus-fix ratio: as long as it stays stable from quarter to quarter, the current pace is enough. If it degrades, debt is growing faster than it is being repaid.