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 solve 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:
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 'guy 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 and the broader business. But do so in an emphatic manner.
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 classes and methods by analyzing the number of functional paths in the code (if/then/else, switch/case, etc) compared to the lines of code used. The higher the cyclomatic complexity, the more difficult code will be to maintain;
- Code coverage: a lack of unit tests is a source of technical debt. Most IDEs and CI-servers can calculate the amount of code covered by unit tests. 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;
- Cost of Delay: this (mostly manual) metric helps to make visible how much time a team loses due to technical debt;
- 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;
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 to inspire you (which took me about 30 minutes to set up):
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 hese metrics are sometimes rough and imperfect, thats no reason to discard. 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
Finally, but most importantly, make technical debt transparent. Don't hide it from the Product Owner or the broader organization. Identify specific improvements, estimate them and suggest them for inclusion on the Product Backlog. Treat them as a regular item for the Product Backlog; break down large items as needed and prioritize them with the Product Owner. 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 reserve a (structural) percentage of the time in a Sprint for dealing 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.
Technical debt can be a frustrating and de-motivating topic for many Development Teams. The keyword is transparency. 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 the Product and/or Sprint Backlog. Don't hide Technical Debt from the Product Owner and the broader organization.