Technical debt is dead

lots of wooden spoons

After more than twenty years working in software engineering, the words "Technical Debt" started to lose their meaning, at least in practical terms.

We use software that runs on technical debt. — some random person online

Ward Cunningham coined the term in 1992 as a metaphor that equates software development to the financial world: every time we ship "first-time" code, we accrue debt. It's the price to pay to ship code fast, and it's a tolerable debt as long as it's repaid immediately.

The current situation is much more nuanced than this, and debt is effectively intrinsic and constant, not just a problem with shipping code fast.

Some examples off the top of my head include:

  • Work that didn't make the delivery deadline.
  • Suboptimal implementations (and not necessarily due to insufficient or unclear implementation details).
  • Inappropriate architectural implementation (e.g. implementing a feature without understanding/looking at the context where this feature sits)
  • Lack of tests (or testing strategy)
  • Lack of alignment with standards
  • Last-minute changes (potentially)

To add to this list, let's not forget that software and the system it resides in evolve over time.

It's like looking at two faces of the same medal: behind the scenes, most modern software abstracts away and delegates many of its complexities to dependencies that help solve some niche and specific problems. In a recursive matter, most of these dependencies do the same for the same reasons.

On the surface, the debt of user-facing products will be affected by the evolution of usability patterns and user needs: the user problem you have solved today might not be the same tomorrow.

Ignoring any of the above can only cause problems. Knowing how to handle it is the key to a team's psychological safety and success.

In a team I joined a while ago, the initial assessment of the code, corroborated by the engineers, revealed that there has never been any architectural plan or organisation (big ball of mud type of thing) if not from the very timid structure imposed by the framework they were using.

Working on any part of the code base was extremely painful and lengthy.

This was also exacerbated by many old dependencies that nobody had taken care of updating in ages, which resulted in a difficult situation: the need to update them due to security vulnerabilities was hindered by a series of breaking changes caused by Major release updates.

The team was frustrated as they were conscious of working on outdated code while the rest of the world was moving forward. Furthermore, they were likely never to hit any of the deadlines Product liked to place, if not by shipping more and more first-time code.

Donts

Some of the solutions I saw firsthand that teams adopted to deal with technical debt were either one of the following:

  • By not looking at it.
  • By "ingesting" some of it every sprint (or, more likely, when there's time or the need for that).
  • By agreeing to spend a fixed amount of their time addressing it.

Let's look at them in a bit more detail:

I don't want to dismiss "not looking at it" immediately. I have seen its usefulness when working with PoCs (Proofs of Concept) or short-lived projects. Outside of these cases, it is one of the greatest threats to the team's stability and the easiest to agree on to avoid.

For the two other cases, there's an implicit requirement that the team needs to know what their "Technical Debt" is.

Secondly, in my experience, these two approaches don't help that much, especially if the team introduces technical debt much faster than tackling it.

Even then, these solutions are usually seen as hampering the team's "velocity" (not a term I like, but I hope you can understand what I mean) because all technical debt will require discoveries, discussions, refinements, and, above all, ownership of the codebase, especially on areas other teams share and use.

This usually leads to someone saying:

There is too much technical debt!

or

The team is swamped with technical debt!

Sometimes, this is paired with:

Stakeholder Y does not understand why the team has so much technical debt.

The glimpse of hope I saw in the eyes of the engineers when I suggested to start looking at our backlog showed how important this step is. For them, having someone who could help them handle the chaos they were living in with the never-ending pressure given by the project manager gave them a breath of fresh air. Nonetheless, the work needed to happen on two fronts: convincing the PM that they would benefit from this and putting together a plan with the engineers.

Team process and handling debt

First, I stopped calling everything "Technical Debt" and helped engineers do the same. Better yet, I helped them understand how to use these words appropriately.

Technical debt will still be useful for internal communications within the department as a metric for understanding at a glance how "good" the solutions we implement are and the status of the code base.

But don't abuse it: it can cause misunderstandings or misrepresentations of the current state.

In more practical terms, I started going through the backlog with the rest of the engineers. Sometimes, this meant the most senior engineers were the most involved, but I didn't want to make this an exclusive process; less experienced engineers will benefit a lot from learning about it.

This initial phase was mostly an iterative process:

  1. Label all the technical debt tickets with a single, clear label, like "tech-debt".
  2. Group them by macro areas, e.g. "architecture", "best practices", "maintenance", or "domain X". This will allow engineers to see their areas of interest at a glance.
  3. Group separate work items that address similar issues under the same initiative (e.g., epic, if you're using Jira).
  4. Decide how vital each initiative is and what its impact, priority, and complexity are. This should help you understand whether it needs more discussion before tackling it. Understanding the direction of the product, team, or company might be fundamental.
  5. Capture anything this additional clarity might bring and see if it fits the picture.
  6. Once I reach this point, I can start a discussion with the whole team to determine what goes first, how much work remains, and what the initiative's blockers and complexities are.

In the process, I review the tickets and ensure the details are precise. This is usually the right moment to review and agree on a ticket format, in case this hasn't been done already.

I then start raising this with my peers, if needed, but especially any product lead, to ensure transparency in priorities.

Moving forward

While working with my teams and through the work done with my mentees, I realised that keeping everything in the backlog might not be the smartest choice.

This may be opinionated to some, but I prefer to keep only things that can be worked on soon in the backlog. The rest, I found, has the chance of becoming stale.

I have stumbled on well-thought-through and detailed initiatives for systems that are no longer active way too many times.

I also have stumbled countless times on tickets that expressed outdated concepts or disused practices.

What do I do when I hear someone coming up with an idea?

What if we reworked this whole process to optimise...

"Coolissimo", I say! "Let's discuss!"

I scheduled a regular meeting to discuss these upcoming potential topics, gather interest, and evaluate solutions. This may lead to creating one or more RFCs, finding support from Product people, and finally, breaking it down into one or more tickets to be worked on soon.

The team took about one month to create their first significant PR addressing some low-hanging fruits. Only after six months did they realise the direction was clearer and understood the value and clarity of our work and process.

As usual, the uphill battle mainly was around teaching and informing non-technical audiences and clearly showing the work's impact on the team's morale and results.

I cannot deny this process might end up being tedious. It requires constant adjustments, and its iterative Kaizen nature makes it hard to showcase for short-term successes. It also shows the importance of having an engineering manager who can help understand and allow the team to work as one, improving their ownership and accountability.