This post originally appeared on Tandem's blog.
“Technical debt” is a metaphor to illustrate that as code ages, it takes more work and becomes more expensive to maintain. That extra work is the interest you’re paying on your debt.
Technical debt in software projects is unavoidable. It’s the cost of doing business, and teams should not be made to feel guilty for choosing to accrue some debt. The key to maintaining a healthy codebase is proactive debt management.
In my experience, there are three main causes of tech debt:
- The team prioritizing shipping code over quality code
- Updated requirements making the past solution harder to work with
- Natural deprecation over time
Since tech debt is an inevitability, when is the best time to pay it down?
Cause: Prioritizing Shipping
Management Strategy: Pay It Down ASAP
Sometimes, choosing to ship a less elegant solution means getting a feature in the hands of your users faster. Decreased time to market is a huge competitive advantage that quickly provides you and your team valuable feedback. I always recommend user testing prior to feature deployment — but sometimes, user testing in a vacuum can only get you so far. The sooner you get your feature into real-life use, the sooner you can identify opportunities and fix any problems.
As long as the decision to ship less-polished code does not risk team burnout or cause unrealistic timelines, taking on this tech debt may be a wise decision. Prioritizing shipping reduces your time to realization and helps you understand your users’ needs better.
For this kind of tech debt, I recommend paying it down as soon as possible. In software development terms, this strategy follows the “make it work, then make it good” mentality. After your team ships a feature, clean up the solution and pay down tech debt. This capitalizes on their knowledge while the context is still fresh and will make the code easier to extend and build upon as you get feedback from your users.
Cause: Updated Requirements
Management Strategy: Highest Interest First
Tech debt can also accrue as you learn more about your needs and user requirements. The solution you started with may no longer be the best solution to meet those needs. Code reflects the team’s understanding of the problem at a point in time: as that understanding evolves, so should the code.
For this type of debt, it’s best to pay it off in a “highest interest first” approach. That means the most painful and most resistant-to-change parts of the code should be refactored first. Be on the lookout for the moment when changes in the codebase that used to be simple and easy become time-consuming or cause some grumbles from the team. When a simple requirement change or feature request means a week or more of work, that’s a good sign that the previous solution or abstraction in the code is no longer suited to your needs.
When this happens, prioritize time to refactor this code, focusing on high-churn areas first. High-churn code is code that is updated frequently. Parts of your codebase that are only touched once per year are less likely to require refactoring. If it works and you don’t change it much, it’s ok to leave it alone.
If you know that you have a new feature coming up, talk with your team about any refactors that should be made before tackling the new work. Building new features on a sturdy foundation is almost always quicker, easier, and more enjoyable than trying to put band-aids on a shaky base. Refactoring the code you’ll be extending before you add your feature helps you fortify it for upcoming iterations as well.
By prioritizing painful parts of the codebase for debt management, you ensure that your team never spends more time (and budget) than necessary maintaining them.
Management Strategy: Pay As You Go
Finally, some tech debt happens naturally as a codebase ages. Languages and frameworks are updated, packages are deprecated, security patches are released, etc. This type of tech debt should be paid down early and often to prevent it from piling up–the more debt there is, the more interest you’ll have to pay on it.
Every team should set aside time in their workweek for managing this kind of tech debt. Budget time for upgrading languages and frameworks when new releases come out. Run security audits every sprint or two and give your team time to remediate any findings. Keep an eye on third-party packages and upgrade them as needed or replace them with alternatives if any become deprecated (hopefully a rare occurrence!)
By paying down this natural debt as you go, you prevent it from ever becoming unmanageable or interfering with the feature work of your team–the work that brings in revenue. 🥳
Reducing Technical Debt in the Future
There’s no way to prevent technical debt in a software product. However, there are some ways to reduce how much debt is accrued. First, as we’ve talked about, is to proactively manage your debt and pay it down regularly.
You should also embrace and celebrate quality code. A little upfront investment in your software can go a long way to making tech debt easier to address when it does come up. And encouraging engineers to “leave the codebase better than when you found it” creates a reinforcing feedback loop of cleaner and stronger code that becomes easier to work with and maintain.
Finally, share long-term goals and product plans with your engineers so that they can provide strategic ideas on where to pay down tech debt and when. Most engineers I know do not enjoy working in brittle codebases: your team is your biggest ally in identifying and mitigating tech debt long-term.