Dealing with Technical Debt
This is the Part III in the blog series Technical Debt – Deal with it Now or it’ll Deal with you. Catch up with Part I: What is Technical Debt and Part II: Identifying Technical Debt.
Hopefully by now it has become apparent that it is nearly impossible to avoid accumulating technical debt while building software and systems. The question is not so much whether or not you are going to take on technical debt but rather how quickly, what kind and what you are going to do about it in the long term. Just like consumer debt, technical debt will show the magic of compound interest but in reverse – just as small investments to a 401k over time will build up a nice nest egg for retirement, a bit of technical debt added to consistently over time will result in a nearly insurmountable pile of technical debt. Taking on a responsible amount of technical debt along with schedule payments will kept your leverage low and still enable you to make those large purchases that are necessary for large product development.
Preventing Unplanned Technical Debt
Developing software without a concerted long-term quality plan including accepted development standards, regular quality checks and a unified understanding of what quality code is will result in chaos and debt as quickly as any band of cowboy coders will. One of the most important social compacts amongst a team of application developers is a solid Definition of Done – a Definition of Done (DoD) is a checklist that enables a team to say “this feature is shippable”. Key elements of a DoD include an agreement on extent and type of tests, acceptable code style, degree of peer review, amount of documentation and acceptable frameworks and versions. A rigorous DoD will make the entire team aware of the level of quality expected of committed code – and that not meeting the DoD will likely result in incremental technical debt and thus requires more work.
Pair programming and peer reviews are both highly encouraged Agile methods of vetting the approaches, consistency and elegance of code. Both mechanisms enable more senior developers to mentor apprentice developers and for apprentice developers to make senior developers aware of new technologies. Having a second pair of eyes and hands on the code base tends to bring down the level of technical debt since inadvisable code is likely to jump out to someone new to the code – who, by the way, did not author it.
There is a practical limit to the amount of pair programming and peer reviews that can be done on an ongoing basis in most shops. For this reason, Mercury has found automated pipelines with quality checks to be a reasonable base level proxy and a timesaver to guard against common stylistic failings, reduce the amount of naïve technical debt introduced into a product’s code base and increase code consistency. Tools like Grunt, Gulp and SonarQuebe along with associated code linters will help point out sloppy code or approaches counter to the organization’s DoD – and since it is done by a machine upon every check-in there is essentially no overhead added to the development effort.
Keep Your Planned Debt Responsible
Just as you should avoid large levels of debt on high interest credit cards, developers should strive to avoid knowingly take on high interest technical debt. High interest technical debt is the kind of technical debt that is present in a module that is often modified or depended on by other modules and is either brittle/difficult to change or throws a lot of exceptions/performance problems. Rather than having expensive code routines that carry out single operations we have now spread the indebtedness throughout the application and this debt will cost the team money every day as we add features. Conversely, those parts of the app that aren’t frequently touched can be prioritized lower, even if their implementation is considered to be particularly poor.
When Your Credit Rating Gets Bad
With consumer debt a poor enough credit rating will render you unable to buy a new car or finance a home and remedial credit action is needed to get that new car or home. Such is life with technical debt in software also. The software team needs to stop new feature development and remove the technical debt so that not only stability will come back to the product but so that additional features can be added without immediate breaks to prior functionality. Consider making these kinds of lump sum payments against the software mortgage that is your technical debt so that the application’s credit rating returns to a reasonable level and typical spending habits can resume. Know, though, that many experts consider this a “condition critical” move only and not one that should be leaned on regularly.
Handling Technical Debt in Agile
The best method for handling technical debt I have ever heard of comes from “Uncle Bob” Martin and leans on the old Boy Scout rule: “Always leave the campground cleaner than you found it.” By intentionally improving the environment for the next group of campers (developers) the team can move into the next sprint knowing that they do not need to dedicate the first part of the sprint cleaning up a technical mess from the prior sprint.
Team members must be empowered and culturally encouraged to clean up a technical mess they identify while working on adding new features. This debt service should only take place to a reasonable threshold, though, rather than completely re-factoring and re-implementing a routine while simultaneously developing the committed new feature. While such refactoring is admirable it’s not often realistic – do enough to make the situation better and flag any extreme problems as a future user story in the product backlog.
Which brings us to the topic of “balloon payments”. In his book Essential Scrum, Kenneth Rubin warns against the practice of regularly carrying out an entire sprint for refactoring or technical debt reduction work (a balloon payment) on an application. In Rubin’s opinion the regular use of balloon payments subtly encourages the team to accumulate technical debt, avoid dealing with technical debt as they find it and rather postpone to a concerted balloon payment sprint (which may never come). This practice can also lead to regressions because many areas of the solution that were not recently developed are touched in a very short amount of time during the “bug hunt”.
Instead, Rubin recommends that the team handle technical debt while performing client-valuable work within each sprint. One example of doing this is address trouble tickets that come in from the field during a sprint. When technical debt is found to be at the root of the ticket it should be flagged as such and then the team should determine if can be worked into the current sprint. Another in-sprint debt handling approach is that when a feature is being added or extended to take note of any technical debt (particularly if it is impeding progress) and refactor or otherwise reduce debt while constructing the new feature. Not only is this likely to be a more efficient approach but should also improve the bandwidth committed to thinking about removing the technical debt in the most optimal fashion.
Finally, another ongoing method to actively reduce technical debt is to write stories and add them to the overall product backlog on a regular basis and assign a percent of the product budget to address this identified technical debt. In this way the Product Owner can occasionally work high priority technical debt stories into sprint backlogs.