Technical debt is one of the greatest frustrations and de-motivators of the Development Teams I work with. Teams generally have no difficulty summoning up examples of technical debt in their codebase. Shortcuts in the code, low-quality code, temporary-but-not-really workarounds and other hacks that may bring short-term solutions but guarantee long-term pain. Development Teams are often acutely aware of the accumulation of technical debt, but feel powerless and unable to explain why technical debt should be a priority. Instead, they feel that ‘business keeps adding more features over stabilizing the foundation’.
In this post I offer four practical tips on how to make technical debt transparent and how to help ‘business’ make a better trade-off between code quality and new features.
There’s also a podcast episode based on this post. So if you prefer to listen instead of reading, then this is for you.
1. Use powerful metaphors
‘Technical debt’ is a powerful metaphor. Use it as such. The consequences of writing hacks & workarounds to ‘help us now, but hurt us later’ are very abstract and incomprehensible for people who are not developers themselves. They simply lack a useful mental model. Continuously emphasizing that ‘X should be improved’ or ‘Y is bad code’ is not going to create understanding. In fact, it will probably put you in the ‘person who always complains’-box at some point.
Metaphors are powerful tools to create understanding. ‘Technical debt’ is such a metaphor itself. Originally coined by Ward Cunningham, and further expanded upon by Martin Fowler, it equates the writing of low-quality code to incurring financial debt. When low-quality code is written with the promise of improving it later, technical debt is incurred with the promise of paying it back later. But the nasty thing about debts is that you have to pay interest. And the greater the debt, the greater the interest. Until you find yourself spending all your time dealing with the interest. In the case of technical debt, the interest represents the time spent (or more accurately: wasted) on dealing with the fallout of incurred technical debt. Like bugs, code that is hard to understand and/or change, code that easily breaks and security risks. Sometimes we’re ok with some technical debt, and the resulting interest, but more frequently we’re not. The following visualization explains technical debt, and the consequences, even more poignantly:
2. Take responsibility as Developers
Some Development Teams feel victim to the way that ‘the business’ keeps prioritizing new features over improving the codebase, while on the other hand holding them responsible for bugs, broken code and the results of technical debt. The Development Team can do no right.
An important step is to stop acting like a victim. Take responsibility for (maintaining or improving) code quality as a team. This is not coincidentally heavily emphasized by the Scrum Guide. Use the tips in this post to put ‘technical debt’ on the agenda and make it transparent. Push back when you, as a team, feel that code quality is dangerously sacrificed in favor of adding more features. Make sure that any estimates that the team generates include the work that is needed to write sufficient-quality code. When necessary, extend the Definition of Done with aspects related to fighting technical debt (e.g. ‘code coverage should at least remain stable when new features are added’ or ‘code should be in accordance to our coding conventions’). Always make technical debt a part of the dialogue with the Product Owner, your stakeholders and the broader business. But also be emphatic to their needs.
3. Use Code Metrics to quantify Technical Debt
Metrics offer a wonderful opportunity to make something subjective and abstract more objective and tangible. It also gives you a measurable goal to improve towards. Several metrics that are useful and broadly available:
- Cyclomatic complexity: This metric quantifies the complexity of your code by analyzing the number of branchings in the code compared to the total number of lines of code. An if/then/else statement, for example, has two branchings. A switch/case statement has as many as there are cases. The higher the cyclomatic complexity, the more difficult code generally is to maintain.
- Code coverage: Code coverage is the percentage of your code that is covered by unit tests. A lack of unit tests is a source of technical debt, as unit tests both document and verify how classes are supposed to work. Most IDEs and CI-servers can calculate code coverage for you. Going for 100% shouldn’t be the goal, and easily leads to a lot of useless unit tests that take a lot of time to maintain. Instead, focus on unit tests for business rules and classes with important logic. A good rule of thumb is to make sure that the coverage stays at least the same when new code is added.
- SQALE-rating: a metric that offers a broad evaluation of software quality, based on a number of internal rules. The scale goes from A to E, with A being the highest quality. Most specialized code quality plugins can calculate this rating (see below);
- Number of rule violations: this metric calculates the number of rules violated from a given set of coding conventions. Violations are usually grouped in categories, from critical to minor
- Bugcount: as technical debt increases, quality of the software decreases. The number of bugs will likely grow. Monitoring the number of (critical) bugs that pop up is a simple but useful metric to track;
- Cost of Delay: this (mostly manual) metric helps to make visible how much time a team loses due to technical debt;
There are many tools and metrics available to quantify technical debt. Most of these tools are based on ‘static code analysis’ and can often be run from within the IDE or from build- & integration-servers. I’ve listed a few below as inspiration.
NDepend is a plugin for Visual Studio (.NET) that offers a boatload of metrics. Especially useful are the SQALE-rating (from A to E), the percentage of technical debt incurred so far and the number of days needed to repair this (a rough estimate, of course). NDepend can also generate a heatmap of the classes that are prime candidates for refactoring — very useful. The Visual Studio IDE (starting from Professional) also offers a number of built-in metrics, like Cyclometic Complexity.
Another option is SonarQube. This platform easily integrates with most CI-pipelines and IDEs and automates a huge number of code metrics (including the SQALE-rating).
Although these metrics are sometimes rough and imperfect, that’s no reason to discard them. The metrics do help people understand the consequences of technical debt, and helps them make more informed decisions about code quality. Teams that inspect these metrics (at least) during the Sprint Review tend to have excellent and balanced discussions about technical debt. A far-cry from the lack of understanding that often characterizes teams that don’t use these kinds of tools.
4. Make Technical Debt transparent on the Product Backlog
Don’t hide technical debt from the Product Owner or stakeholders. Identify specific improvements — like refactors, bugfixes and quality improvements — estimate them if you need to and suggest them for inclusion on the Product Backlog. Treat them as just like other items on the Product Backlog; break down large items as needed and help the Product Owner order them . This helps the Product Owner make a conscious decision about how to deal with technical debt.
Another option is to agree with the Product Owner to set aside a percentage of the time in a Sprint to deal with Technical Debt as the Development Team sees fit. There is a risk in this approach that Technical Debt (and what is being done about it) remains invisible to everyone outside the Development Team. So make sure to create items for the specific improvements and put them on the Sprint Backlog during the Sprint Planning.
Low quality code is frustrating and demotivating for many Development Teams. The keywords are transparency and responsibility. Explain the cost of low-quality code by using the transparent metaphor of ‘technical debt’. Make technical debt visible in the code using a variety of objective metrics, and frequently re-evaluate these metrics. Finally, make technical debt visible on your Sprint Backlog and Product Backlog. Don’t hide Technical Debt from the Product Owner and the broader organization.
But transparency alone is not enough. Development Teams should also take the responsibility for reducing technical debt. While balancing the needs of the stakeholders with the quality of the code, they should prevent the accumulation of technical debt over time and explain how and why this impacts stakeholders. Together, they can then create amazing products.