Technical Debt
Every shortcut in code is like a loan. Today we go faster, but later we pay its interest with every change. Record the debt, first fix the place that is both complex and changes often, and talk about it in the language of time and money.
Author: bezzad
The problem: why has everything become slow?
Our online shop started two years ago. In those days, each new feature took one week. Now the product manager asks: “Why did a simple change in the discount take three weeks?”
The team knows the answer, but it is hard to say:
- The discount rule is copied in three places. In the shopping cart, on the payment page and in the sales report. Each change must be done three times.
- We have no automated tests for payment. Each change must be tried by hand.
- The CheckoutService class is three thousand lines. Nobody dares to touch it.
Each of these was once a quick decision. We call these technical debt.
The idea: loan and interest
Ward Cunningham introduced this comparison. When we take a shortcut, it is like taking a loan:
- The loan principal. The work we must do later to fix the code. For example, merging the three versions of the discount rule.
- The loan interest. The extra time we pay every time we work with the bad code. For example, three changes instead of one, and bugs that happened because one of the three places was forgotten.
- The important point. Until we pay back the loan, we pay the interest. And the interest is only paid when we touch that code.
A loan is not bad. Companies take loans to grow. The problem is a loan that nobody remembers, and interest that grows every month.
Live example: debt interest over time
In this simple model, we have ten units of time in each sprint. Each sprint adds one new shortcut. Each unit of debt eats one unit of time in every sprint. Try the two methods:
These numbers are made up. They only show the general shape.
Look closely at the first sprints. The “features only” method is ahead at first. This is why this method is tempting. But a few sprints later, the debt interest eats most of the team’s time.
Four types of debt
Not all debts are the same. Martin Fowler splits debt with two questions: was it deliberate? and was it prudent?
- Deliberate and prudent. “The sale is on Friday. We ship with a simple way and fix it next week.” This is a correct business decision, if it is really recorded and done next week.
- Deliberate and reckless. “We have no time for design.” The team knows it is bad, but has no plan to come back.
- Inadvertent and reckless. The team does not know the code is bad. The solution is training and code review.
- Inadvertent and prudent. “Now that we built it, we understand how we should have built it.” This always happens. It is a sign of learning, not a mistake.
Which debt should we pay first?
Not all bad code is worth fixing. Remember? Interest is only paid when we touch the code. So ugly code that nobody changes has almost no interest.
A simple way to find these hot spots (Hotspot):
- Count the changes. The git history shows which files changed most in the last few months.
- Look at the complexity. Very big files, very long methods, nested conditions.
- Put these two side by side. A file that is high in both lists is the first priority.
- Ask the team. “Which part do you not like to open?” This answer usually matches the numbers.
# Files changed most often in the last 6 months
git log --since="6 months ago" --name-only --pretty=format: -- "*.cs" \
| grep -v '^$' | sort | uniq -c | sort -rn | head -10
Example: merging the discount rule
Before the change, the same logic was in three separate classes:
// CartService.cs, CheckoutService.cs and SalesReport.cs (copied 3 times)
var discount = order.Total > 5_000_000 && customer.IsVip ? 0.1m : 0m;
var payable = order.Total - order.Total * discount;
After the change, the rule is in one place and in business language:
public static class DiscountPolicy
{
private const decimal VipThreshold = 5_000_000;
private const decimal VipRate = 0.10m;
public static decimal PayableAmount(decimal total, bool isVip) =>
isVip && total > VipThreshold ? total * (1 - VipRate) : total;
}
Now one test is enough, and the next change happens in only one place. This Refactoring was small. We did not need to rewrite the whole system.
How do we reduce debt?
- Record it. Each deliberate debt becomes a task in the team’s task list (Backlog). Write where it is, what its interest is, and how much time fixing it takes. Unrecorded debt is forgotten.
- The Boy Scout rule. Every time you touch a file, leave it a bit cleaner than before. A better name, a shorter method, a test.
- A fixed share in each sprint. Set aside part of each sprint’s time for debt. Small and steady payments are better than one big cleanup that is never approved.
- Attach the debt to business work. “Before the new discount, we merge the discount rule” is approved more easily than “one sprint for Refactoring”.
- Change a big part little by little. For a very bad part, a full rewrite is dangerous. Build the new code next to the old code and move the paths to it little by little. We call this method Strangler Fig.
How do we talk to the manager?
The product manager does not understand the words “dirty code”. But they understand time, money and risk very well.
Bad
- “The code is very bad. We must rewrite it.”
- “We need Refactoring.”
- “This architecture is old.”
Good
- “Each change in the discount now takes three times the time. With three days of work, we bring this back to one time.”
- “Last month, two payment bugs came from this part. With this work and its tests, this risk goes down.”
- “Before the New Year campaign, this part must be fixed. Otherwise every campaign change is slow.”
Common mistakes
| Mistake | Result | The right way |
|---|---|---|
| Deliberate debt with no record | “We will fix it next week” never comes. | A task in the Backlog with its interest and cost. |
| Fixing ugly code that nobody changes | Time goes away, and no benefit comes back. | Hot spots first: complex and often changed. |
| A full rewrite from zero | Months of work, new bugs, and the old system must still be kept running. | Change little by little, with tests, or the Strangler Fig method. |
| One big cleanup once a year | It is never approved, or it is left in the middle. | A small, fixed share in each sprint. |
| Talking in technical language with the manager | The manager does not understand the reason and does not give it priority. | Time, money and risk. |
| Calling everything debt | The word loses its meaning. Personal taste is not debt. | Debt means code that has real interest: slowness or bugs. |
Summary in six lines
- Every shortcut is a loan. We pay its interest with every change in that code.
- Deliberate and prudent debt is a tool, if it is recorded and paid back.
- Interest is only where code changes. Fix the hot spots first.
- Small and steady payments are better than a big rewrite.
- Record the debt and attach it to business work.
- Talk to the manager in the language of time, money and risk.