Table of Content
All teams struggle with technical debt, and every software team creates a technical debt that it strives to minimize. Some teams are better at repaying this debt than others and succeed in recognizing tech debt’s negative impacts, quantifying possible impacts, prioritizing fixes, and committing to a resolution plan.
What You’ll Learn
- The different categories of technical debt: local, global, and systemic.
- How to prioritize technical debt in an Agile environment.
- Techniques to address and reduce technical debt effectively.
- The role of Agile practices in managing and minimizing technical debt.
- Strategies for long-term technical investment and debt management.
Technical debt drags down software development. It makes code harder to build, run, and deploy and may even cause major production outages. Addressing technical issues is the easy part and usually entails refactoring or changing some code. The hard part is prioritizing and quantifying technical debt. The truth is, software teams fail to resolve debt because they don’t get the ball rolling soon enough, not because they can’t fix technical issues.
Repaying existing technical debt and developing features constantly compete for backlog priority. When transitioning application development from Waterfall to Agile, technical debt is often inherited from old systems and code. There’s no one-size-fits-all solution to manage technical debt, but it’s clear that agile teams must routinely tackle it in many different forms. And working on the wrong issues for a particular type of debt wastes precious resources and time, defeating Agile principles.
The Three Faces: Local, Global, and Systemic Technical Debt
Some debts eventually force teams to declare software bankruptcy, while others warrant simple interest payments. Trivial debts can be ignored since they may be repaid at any point. It is too easy for a development team to fall for the fallacy that all technical debt should be eliminated, but the real-world truth is that only the most untenable debt needs to be discarded.
Technical debt broadly falls into three categories: local, global, and systemic. And it is important to properly categorize each type, to separate the tenable from the untenable.
Local debt
This is self-contained in a single method, class, or function. Imagine SOLID violations applied to a method, class, function, or single file. The impact is only felt when modifying the code in question.
Global debt
This is self-contained in a single application or service. Unlike local debt, global debt involves SOLID violations across subsystems in a larger system that spreads to seemingly unrelated areas. Examples include a mismatch between abstraction layers (presentation vs. data), problems at other integration points, and hard coupling. These issues don’t impact consumers of the application or service but are felt when modifying the service itself.
Systemic debt
This includes spills across multiple applications, infrastructures, or even teams. Examples include multiple services sharing a data store, high coupling across different bounded contexts, and a stark mismatch between technical implementations and business logic. Systemic debts are completely untenable in the long term.
Each category of debt carries different impacts, so they need to prioritize and handle technical debt differently.
Local Technical Debt: Agile Low-Hanging Fruit
Known refactoring techniques like “extract method” or “extract superclass” typically solve the local technical debt. They may even mirror known code katas, which makes them the least consequential or even downright trivial. Let’s take a look at an example:
An engineer wrote code a year ago. The code is overly complicated but covered by tests. It’s not the best code by any stretch of the imagination, but it’s functional. The business requirements haven’t changed, so the code stays as it is.
Engineers may want to refactor the code, but it’s not worth the effort since the code hasn’t changed in a year. On the other hand, local tech debt in frequently modified code inhibits development, as such code is usually at the core of a business and more developers must understand and modify it.
Eliminating debt here is like patching up potholes to maintain a highway’s fast lane. If technical debt is not properly addressed, then engineers will write increasingly brittle code as a workaround, which may eventually create global technical debt.
Static code analysis tools can mitigate local technical debt and running this analysis before committing, as part of the deployment pipeline virtually eliminates it. But you’ll need a different approach to combat existing problems. To start with, it’s good to assign local fixes to new team members as a learning exercise. Teams can also apply the “boy scout rule” that the code should be left better than it was before, a practice that works well since code improvements are proportional to how often the code is modified.
Global Tech Debt: First Signs of Trouble
Global tech debt is harmful. Here’s an example from a typical MVC web application:
The development team isolates complex view layer concerns into a presentation layer, and a requirement comes in that the UI needs to display custom data. The existing middle layers don’t support this, so the presentation layer calls directly to the database. To begin with, this is a leaky abstraction, which may trigger the broken window theory and impact other areas.
This appears in the wild more often than engineers like to admit. Unfortunately, there’s no quick fix for these issues, as a solution requires focusing on all related code. Senior engineers are best suited to repay the debt by carefully separating concerns, creating boundaries, and enforcing bigger architectural concerns.
Judicious governance and workflow improvements can mitigate some of these issues before they appear, and adding careful code review to specific releases is a good starting place. Clearly, not all releases are created equal. Releases that change code across boundaries or rework core business concepts must be vetted carefully to minimize technical debt. Releases that modify a single less-used class do not. These concerns should shift left in the workflow, meaning that engineers should discuss potential technical debt as early in the process as possible in order to mitigate problems later on.
Global tech debt must be attended to when it inhibits the development of frequently modified code. If the solution is not immediately clear, then spike on a potential 80/20 solution and take action swiftly before the situation devolves into a systemic issue. Clean-up will take time out from sprints, so make sure to plan accordingly.
Systemic Tech Debt
Systemic tech debt is the most dangerous form, manifesting itself throughout all SDLC phases. It can seriously slow down development, demotivate engineers, force a ground-up rewrite, or even kill the company. Systemic issues are this powerful because they capture problems at the architectural level. There are a range of issues that arise including:
- Using a NoSQL database where an RDBMS is the correct choice
- Creating tight coupling between independent services
- Communicating across bounded contexts via a shared database
- Splitting business concerns into too many microservices
- Choosing the wrong deployment architecture
Systemic issues tend to sneak up on development teams. They are always there, lurking just under the surface until some innocuous change or request creates a forehead-smacking moment. Then everyone in the room sighs, realizing that something is definitely wrong.
There’s no prescriptive solution to resolving systemic issues. Addressing this technical debt takes teams of engineers, careful planning and execution, and time—the scarcest resource. It’s therefore in the organization’s best interest to combat these problems earlier rather than later.
Building evolvable architecture is an automated way to combat systemic issues. Building Evolutionary Architectures proposes testing key architectural characteristics such as component coupling or durability with fitness functions. The deployment pipeline executes the fitness functions as part of its criteria verification, thus rejecting severely problematic code. If this approach is not possible, then you’ll have to implement strict high-level code reviews with senior engineering staff, to identify issues manually.
Now, at this point, it’s possible to prioritize and craft a strategy to mitigate your organization’s technical debt.
Prioritize the Backlog to Address Technical Debt
Like so many other issues, technical debt becomes a big problem when it’s not attended to. Many organizations ignore the issue, hoping that the problem will resolve itself, or focus their efforts in the wrong direction.
Another common mistake teams make is in accounting for technical debt across multiple projects and the direct as well as indirect impact it can have. For example, technical debt in dependent projects can inhibit delivery across other projects. Agile software development teams must understand that each debt’s worth is a factor of its scope (local, global, systemic), impact on frequently modified code, and time required to fix it. Teams must prioritize fixes and coordinate releases across all project backlogs.
Your strategy to address technical debt should be an 80/20 approach for improving development on the most frequently modified code paths. The goal isn’t to avoid technical debt but to keep it easily manageable and incorporate it into your agile delivery. Another point to keep in mind; if it’s not inhibiting common development activities, then let it be. This keeps the team working in the right direction.
So, addressing inconsequential local debt can be moved to the bottom of the pile, while addressing global tech debt is likely the biggest 80/20 win as it hedges against systemic debt and improves the development of frequently modified code. Prioritize spikes aimed at 80/20 solutions and go from there.
This leaves you with the issue of systemic technical debts. These may take many sprints to resolve and thus must be part of a long-term strategy. Again, prioritize spikes at 80/20 solutions for systemic problems. The spike result should help estimate fixes and prioritize them against other items in the backlog.
Do not aim to wipe out technical debt in a single massive release. The strategy to manage technical debt should revolve around fixing and releasing in continuous small batches. Practically speaking, these releases can in fact bundle multiple local tech-debt fixes.
Global and systemic fixes require multiple releases that are small and focused. Addressing global and systemic debt is complicated, so move slowly and deliberately and avoid including any other changes in releases for these forms of debt. Your release management strategy should also promote visibility into fixes and their relationship with other projects and releases as well.
So, how does Agile manage technical debt?
The first question is, how does technical debt in Agile come about. And there are several reasons including rework that accumulates when code isn’t clean, designed well, or properly tested. Agile teams manage this in one of several ways:
- Continuous Refactoring
- Iterative Development
- User Stories and Backlog Refinement
- Sprint Retrospectives
- Test-Driven Development (TDD)
- Continuous Integration and Automation
- Cross-functional Collaboration
Prioritizing the Future via Technical Investment
Technical investments in automated testing, static analysis, and fitness functions bake quality into the process. Over time, this moves technical debt out of individual backlog tickets and into the current culture. And this kind of agile culture change can mitigate problems to the point where systemic debt rarely appears and prevents speedy delivery.
This future likely feels far away, so you’ll need to start with prioritizing the backlog by considering the different tech-debt categories and the affected code and business flows. You’ll also need to prioritize efforts across all projects since not all projects are created equally. Achieving visibility into all projects and releases with software such as Panaya is a key factor in deciding what to tackle.
Reduce existing technical debt the smart way
Eschew fixing local issues and prioritize integrating static code analysis into the existing test suite to mitigate the creation of more local technical debt. Next, prioritize spikes aimed at 80/20 solutions for the worst global tech-debt offenders. Don’t bite off more than you can chew in the beginning by tackling systemic debts. Instead, craft a technical strategy for mitigating their impact and focus on 80/20 spikes after addressing pressing pain points.
Key Takeaways
- Prioritizing technical debt in Agile involves focusing on the most impactful issues and integrating solutions into regular sprints.
- Continuous refactoring, iterative development, and automated testing are crucial in managing technical debt.
- Addressing global technical debt offers significant improvements, while systemic debt requires long-term strategic planning.
- Agile practices and tools like static code analysis and fitness functions help bake quality into the development process, reducing future technical debt.