The Wikipedia article on technical debt starts with:
Technical debt (also known as design debt or code debt) is a concept in software development that reflects the implied cost of additional rework caused by choosing an easy or limited solution now instead of using a better approach that would take longer.
While true, it fails to mention that lots of technical debt is inherited instead of being the result of prudent or reckless choices.
A lot has been written about preventing the accumulation of technical debt. But what if you take over a project including its tech debt? Where do you start? How do you prioritize tech debt "repayments" versus feature requests and general bugs?
Here are some tips, based on experience in the trenches:
Learn to recognize tech debt
- Are code changes and additions becoming increasingly harder to write and test?
- Do you have to delegate changes to particular team members "because only they know the code"?
- Do even small changes require modifications across multiple files?
- Are you afraid of making changes?
- Do you have to study lots of code, or first ask for advice, before being able to make a change?
- Is lots of code not covered by tests?
If the answer is yes to one or more points you are probably facing technical debt.
Make technical debt important
Once you've recognized the signs of technical debt, you also recognize the consequences:
- Development speed starts to dwindle (in Agile terms, velocity starts to decrease).
- Quality starts to worsen, affecting customer happiness and perhaps even revenue.
- Team morale starts to decline, people start to leave.
- New hires quickly burn out facing your tech debt. They might decide not to stay.
Technical debt is dangerous. It can kill your company.
Start repaying tech debt today
Split bug fixes into two parts (resulting in two pull requests). The first PR contains the bug fix, and the second PR contains refactored code in and around the code where you just fixed the bug. This is an efficient method because by having to study the code to fix the bug, you also see how the code can be refactored (even if it's just adding tests instead of refactoring).
Take your time. If you've worked in this codebase for two years, and intend to stick around in your company for a few more years, and there's a lot of pressure to delivery new features and fix bugs, then it's perfectly fine to carve out one or two years to fix what's broken. But stop accumulating new tech debt in the meantime (see next point).
It has no use to accumulate new technical debt in new code, while fixing debt in older parts. Instead, you and your team have to discuss long and hard 1) how did we get into this undesirable situation, and 2) how do we prevent making the same mistakes tomorrow? The answer is probably a mix of: improve your work item descriptions, resist the pressure to only ship features and bug fixes, conduct serious and detailed code reviews, study well-written (open source) code by others, and hire better developers than you (which is an opportunity to become just as good as they are! Don't see it as a threat!).
Don't make it one person's responsibility to "fix tech debt". Instead, the whole team should pick up refactoring tasks, big and small, depending on their level of experience.
Carve out a minimum amount of time per sprint (or other chunk of time, this depends on your project management methodology). Begin with e.g. 10 of 15% of the sprint duration i.e. 1-2 days if you're doing 3-week sprints.
Don't axe refactoring tasks first when facing a time crunch. You're probably facing pressures from multiple sides to deliver more than you can handle. If refactoring tasks are always sacrificed first then you'll get nowhere (in other words, you'll be giving in to software entropy).
Start small. Begin by breaking up large methods into smaller ones, breaking classes into smaller sub classes, finding and merging ("drying up") duplicate code, and adding tests to untested code.
Definitely don't do the opposite of starting small: a multi-week "tools down" period in which you hope to refactor everything to perfection is probably not going to work. Firstly, this will block the operation from dealing with bugs, and secondly it's highly unlikely that you'll be able to make everything right in one, quick fell swoop this time.
Use a label or tag when adding refactoring tasks to your backlog. Just call it "tech-debt" and use a bright color to make it stand out among other issues.
Whenever you encounter technical debt in your codebase while working on something else, add an issue to your bug tracker in which you briefly explain where and what you found. Label it as tech debt and carry on with what you were doing. In this way you start to document code smells, code duplication, missing tests, and other tech debt issues.
Instead of rewriting code, you should first consider replacing it by a 3rd party API, an external library, or simply dropping the feature. If you can rely on someone else's code, provided is has a solid reputation, you'll be able to cut code instead of having to rewrite it yourself.
Do you know this proverb?
The best time to plant a tree is twenty years ago. The second best time is now.
Transposed to tech debt: it is never too late to start dealing with technical debt. Just begin today!