Technical Debt – Good or Evil?

Technical Debt: Good or Evil?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:

“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….”

Technical Debt is such a loaded, misused and misunderstood term. Click To Tweet

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?”

Wanna know if what you’ve got (or gonna create) is “Technical Debt?”

Ask yourself:

  • 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.

Intentional/Deliberate 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
  • Downtime
  • Learning curve
  • Risk

Tracking Technical Debt

  • Screen Shot 2015-03-22 at 1.22.35 PMCreate 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

tangle of wires 5050998Technical debt in legacy code can be paid in one of two ways:

  1. Refactoring
  2. 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
When faced with two equally tough choices, avoid the third choice: not to choose. Click To Tweet

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!

More

2 Comments

  1. Great article – C. Jaspan and C. Green, “Defining, Measuring, and Managing Technical Debt,” in IEEE Software, vol. 40, no. 3, pp. 15-19, May-June 2023, doi: 10.1109/MS.2023.3242137.

    https://ieeexplore.ieee.org/document/10109339

    Provides a collectively exhaustive and mutually exclusive list of 10 categories of technical debt:

    Migration is needed or in progress: This may be motivated by the need to scale, due to mandates, to reduce dependencies, or to avoid deprecated technology.

    Documentation on project and application programming interfaces (APIs): Information on how your project works is hard to find, missing or incomplete, or may include documentation on APIs or inherited code.

    Testing: Poor test quality or coverage, such as missing tests or poor test data, results in fragility, flaky tests, or lots of rollbacks.

    Code quality: Product architecture or code within a project was not well designed. It may have been rushed or a prototype/demo.

    Dead and/or abandoned code: Code/features/projects were replaced or superseded but not removed.

    Code degradation: The code base has degraded or not kept up with changing standards over time. The code may be in maintenance mode, in need of refactoring or updates.

    Team lacks necessary expertise: This may be due to staffing gaps and turnover or inherited orphaned code/projects.

    Dependencies: Dependencies are unstable, rapidly changing, or trigger rollbacks.

    Migration was poorly executed or abandoned: This may have resulted in maintaining two versions.

    Release process: The rollout and monitoring of production needs to be updated, migrated, or maintained.

  2. Andy Cleff

    https://www.revelo.com/blog/technical-debt

    “Technical Debt: What It Is, Types, and Examples” Technical Debt, an intrinsic aspect of software development, impacts projects in various ways, often with far-reaching consequences. The concept involves the trade-off between expedited development and long-term stability. The provided resource offers a comprehensive exploration of Technical Debt, encompassing its nuances, challenges, and strategies to mitigate its impact on software development projects. Understanding and managing Technical Debt is pivotal to sustaining the health and success of software projects.

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to Top