Bring up the topic of “technical debt” to a software engineer, a product owner, a stakeholder, or heavens above, the gold owner.
What happens? Do you get frowns? Smiles? Nervous fidgeting?
Technical Debt is such a loaded, misused, and misunderstood term.
I imagine in most conversations when the topic comes up we probably aren’t even talking about the same thing.
What is Technical Debt?
Ward Cunningham is credited with coming up with the metaphor of “technical debt” in the context of software development:
Technical Debt is such a loaded, misused and misunderstood term. Click To Tweet
“Shipping first time code is like going into debt. A little debt speeds development so long as it is paid back promptly with a rewrite… The danger occurs when the debt is not repaid. Every minute spent on not-quite-right code counts as interest on that debt….”
Doc Norton has a great video on the topic:
Go Ahead, Have Some Debt
I posit that there’s not a single, in-service, complex software system in existence that doesn’t have technical debt. Taking on debt allows for:
- Rapid delivery
- Quick feedback
- Future corrective design
Debt is like borrowed money. You get to do something sooner (buy a bigger screen TV, a new car) than you might otherwise if you had to pay “cash,” but until you pay it back, you’ll be accumulating interest. So if left unchecked, eventually you will hit your credit limit and your purchasing power will be reduced to zero.
The ability to pay back the debt depends upon you writing code that is clean (and well tested) enough to be able to refactor as you come to understand your problem better.
“Good or Evil?” is Probably the Wrong Question
A Better Question: “Debt or Cruft?”
- Is the code clean?
- Is the code tested?
- Is there a learning objective or event?
- Is there a plan for payback?
- Is the business truly informed?
If you answer “no” to even one, you don’t have Technical Debt – you have a mess, you have cruft: An unpleasant substance, the result of shoddy construction, redundant, old or improperly written code.
Inherited Debt aka Cruft
This is the non-strategic result of poorly written or low-quality code. Cunningham as well as Uncle Bob do not call this technical debt. They just call it crappy code. Cruft. What makes it “debt” for some is that the original authors are no longer around to benefit the organization through learning and improving from their own earlier mistakes.
- The design approach was inconsistent, just error-prone, tangled or incoherent architecture, complex dependencies
- There was a lack of understanding of process or modularity
- Code was written by a less than proficient programmer w a lack of knowledge or collaboration, poor choice of components, frameworks, etc,
- Unclear, unreadable code; style drift; violations of coding standards
- Parallel/duplicate development on separate codebases, uncommitted code or long-lived branches
- Brittle tests, incomplete test coverage
- Insufficient, missing, or out of date technical documentation
- Missing backups
- Security flaws
- It might have been that “we just didn’t know how we should have done it”
Cruft happens. Cruft is a bad decision, every time. It’s a trap. Avoid it. Clean it up constantly. Follow the Boy Scout Rule – leave things better than you found them. Never make an intentional mess. Monitor and clean cruft. But don’t call it technical debt.
Made as a conscious decision to optimize for the present rather than for the future. Hopefully, the decision will have been prudent, rather than reckless. Some prudent examples:
- “We don’t want to take the time to reconcile these two databases, so we’ll write some glue code that keeps them synchronized for now and reconcile them after we ship.”
- “We don’t want to take time now to write unit tests for 100% of the code. We’ll circle back on those tests after this release.”
- “Requirements have evolved, some of the code has become unwieldy, and we will need to refactor to support future requirements.”
Generally speaking, old debt is bad. Unintentional debt is bad, really bad. (See cruft above.)
Good debt, taken on consciously and strategically, can:
- Shorten time to market
- Delay development expense
- Conserve capital/resources
Chronic, out of control, continuously growing debt just plain stinks.Follow the Boy Scout Rule - leave things better than you found them. Click To Tweet
What is Not Technical Debt
- Incomplete work – feature backlog, deferred features, cut features, etc.,
- Known bugs – a problem that impairs or prevents the functions of the product. Bugs may result from technical debt, but they do not “grow” in impact over time. The cost of fixing them and their impact do not compound.
- Stuff we don’t know and haven’t worked with – That’s an opportunity to better understand the business requirements as they were implemented at the time, and then find better ways of implementing them going forward.
- Maintenance debt – the aging of the software/hardware stack. This is tracked and classified separately from deliberate decisions made to expedite a project with plans to pay for those choices at a later time.
None of the four bullets above require interest payments.
Thoughts About Maintenance Debt
By spending resources on maintaining our software/hardware stacks:
- We ensure that the stack has fewer defects and is actively supported
- We have uniformity which in turn leads to better reuse (of people, processes, tools, …)
- We have access to skills that are currently in the market (both in terms of actual availability as well as aspirational)
- We have better velocity – when a project is not actively maintained, we waste a lot of time in research when we do need to add features.
The “debt metaphor” probably works here – but perhaps we could view this as depreciation.
What are the Costs of Technical Debt?
The interest on technical debt increases software entropy. And ongoing development on a code base with technical debt will compound, increasing the cost of “paying off the debt” in the future. Costs include:
- Lack of velocity for new features
- Increased probability of new bugs
- Developer frustration
- Opportunity cost
- Learning curve
Tracking Technical Debt
- Create a card (or edit an existing one), and call it “Technical Debt.” Corollary: If the “shortcut” you are considering taking is too minor to add to the debt-service backlog by creating a JIRA ticket / GitHub Issue, don’t take that shortcut in the first place!
- Create a clear definition of done
- Create subtasks or related tasks
- Estimate it
- Have a plan/timeline for pulling the card into a future sprint. Don’t make the backlog the kitchen junk drawer.
Habits to Help Keep Technical Debt Under Control
- Avoid debt in the first place by writing clean code (the obvious route): “Code that is as simple as possible and as understandable as possible. Code that is so easy to understand that defects cannot hide in it. Changes can be made easily, with no side-effects.” Put in place and stick to coding standards, lint check.
- Stay keen: Sharpening the Saw
- Start w complete test coverage
- Document, document, document. Write the readme first. And then keep docs up to date during development.
- Amortize debt payments for long-term debt into each iteration: Budget capacity to keep a backlog well-refined, and include on average 10-20% of a sprint’s planned capacity on vanquishing the most “profitable” technical debt first. (See “Quest” below)
- Do a short-term debt reduction iteration: Negotiate “OK, we will do ‘x’ now, but we will need to reserve one sprint to clean up the mess after launch.”
- Separate epics – At a certain threshold (more than a sprint) debt reduction should be approached as a discrete epic
Approaches to Correcting Debt-laden Legacy Application Code
Technical debt in legacy code can be paid in one of two ways:
- The “big rewrite”
Both will take time.
And with debt-filled legacy code, refactoring can be really hard. To refactor we’ll need to be confident that our changes are not going to break anything. And to write tests on top of that code sucks because the code is often so tightly coupled. So we have a catch 22: to refactor, we need tests, to test, we need to refactor. One solution to debt in legacy code will be improving the current code base in cycles: Introducing tests and breaking monolithic legacy apps into uncoupled pieces. For more, see the classic text: Working Effectively with Legacy Code
A big rewrite won’t be a bag of fun either. It will almost always take longer, and be a more difficult path than we expect. Maiz Lulkin has a great post on this (Technical debt 101)
When developers propose a big rewrite, and for whatever crazy reasons, business agrees, the stage is set for a new kind of failure. Business starts by asking if all functionality will remain in the new app. Energized by the prospect of making things right, and overconfident, developers say “yes”. It turns out that it’s virtually impossible to track all functionality in a legacy code base, and few rewrite projects take the huge necessary time to document everything.
The second thing business will ask is about deadlines. Estimating a big rewrite is probably one of the most unrealistic things one can try to do.
The third thing is that business will not accept all new features to be halted. Therefore there will be the need to keep track of them, and to reimplement them as well. And all relevant data should be migrated.
Fourth, in the rush to convince business, developers will promise all kinds of things, like that the refactoring will make the system faster, more robust or scalable…
Fifth, given part of the problem was developer’s inexperience in coding itself, how can they guarantee that now they know better? Will there be new, senior developers, or maybe consultants, helping them?
Sixth, generally, planning is not a particular strength of the kind of project that ends up needing a rewrite. Will the rewrite be properly planned?
How to Keep the Quest for Perfection from Causing Paralysis
Refactor or Rewrite? What to do? Well, when faced with two equally tough choices, avoid the third choice: not to choose. We should never hear: “Don’t touch that code. Last time we did, we spent a week fixing it…” (There’s one possible exception to avoiding the third choice, that is when something is approaching End Of Life. At EOL the tech debt payments might just go away when we pull the plug…)
Spend a great deal of time in discovery. Learn where the dragons are. Consider the big picture. Make your assumptions explicit. Determine the true lifetime costs of the options.
And then budget sufficient resources to pay down the most profitable debt in the most expedient way possible:
- Pick one mess
- Don’t pick the easiest
- Don’t pick the most fun
- Think improvement, not perfection
- Be brave. Don’t succumb to fear. Don’t be afraid to break stuff. JFDI
Czechoslovak Animated Movies That Speak Best of “Technical Debt” – from DZone
Here are several Pat & Mat videos. Enjoy watching technical debt in the making, and try to avoid doing this in your projects.
HT to @BradStokes for the sharing of Pat & Mat!
- How to deal with technical debt – Robert van Lieshout
- Ward Explains Debt Metaphor
- Construx Technical Debt – and a webinar “Managing Technical Debt“
- Martin Fowler, 2003 and 2009
- Andy Lester, YAPC::NA 2006
- Good and Bad Technical Debt (and how TDD helps)
- Technical Debt Debate, with Ward Cunningham & Capers Jones
- Organizational Debt Is Like Technical Debt – But Worse