This post lists the key insights from this article, from Jon Thornton, engineering manager at Square.
“Tech debt” is a dirty word in the software engineering world. It’s often said with an air of regret; a past mistake that will eventually need to be atoned for with refactoring.
If we define tech debt as work that will have to be done in the future, we can track spending in terms of the time that will be spent on that future work. We can also “invest” time in work done now.
This mental model can help avoid spending foolishly and paying in maintenance for a system you can’t afford, and it can give you a way to see opportunities to use tech debt intentionally. Here are a few cases where taking on tech debt helped my projects succeed.
Several self-imposed guidelines can help the implementation of our scaffolding go smoothly:
- We committed to our estimate. If we couldn’t complete the scaffolding in the estimated timeframe, it was a sign that we were taking on too much complexity and needed to rethink our approach.
- It was designed to be thrown away, and communicated as such from the get-go. The code was well contained, and we didn’t waste time nitpicking over minor implementation details.
- We understood the scaffolding’s limitations, and avoided using it in a situation where a failure would cause harm. We’d be going deeper into debt than expected if we spent time cleaning up errors caused by the scaffolding, so we were very intentional about the testing situations the scaffolding was used in.
- Stakeholders knew we were deliberately taking on debt. We bragged about these limitations with our stakeholders and users as if they were features—“Look at all the time we saved!”—and in the process made sure they knew we would need to invest time later to build the real email delivery system.
Scaffolding can help you adjust the order in which you build parts of your application by filling in for dependencies, so that you’re not stuck building components before you have a way to validate them. Our throw-away email sender got us feedback on the email editor while the code was still fresh in our minds, helping us iterate faster and perfect our hardest problem. When it came time to build the real email sender, we did it with lessons learned from building the scaffolding.
2. Hardcoding Things
We should consider a few factors when deciding whether to hardcode something:
- Is it okay for changes to take several hours to take effect? Hardcoding comes with a clear limitation: changes require deploying new code to production. Failing to acknowledge limitations can tip something into a maintenance headache, creating bad tech debt.
- Is Git an acceptable update user interface? Updates will require interacting with your team’s version control tool, and maybe the CI system. To avoid introducing additional coordination or training overhead, we use the test: is there someone already involved in the process who knows Git?
- Are there existing UI, validation, and data patterns? Hardcoding brings the biggest benefits when it helps you avoid defining new patterns. If your application already has patterns that cleanly solve your problem, hardcoding might not be worth the drawbacks.
Allow-lists, form field options, and feature flags are other patterns that you can usually consider hardcoding.
3. Not Fixing All the Edge Cases
The author is not saying quietly leave it there for the next developer to deal with! Being intentional here meant answering some questions:
- What happens when there are 11 items? Well, not much, actually. It was an arbitrary limit and the rest of the app doesn’t care about a few extra items.
- Can we find out if this race condition happens more often than expected? Yup, a simple database query found accounts with more than 10 items, and each item record had a timestamp. Turns out this race condition didn’t occur in production.
We were better off leaving this tech debt unpaid. Because the impact of creating an extra item was low and easy to monitor, we could spend time on higher-priority work instead of addressing a practical non-issue.
Good Tech Debt Is Intentional
A lot of bad tech debt comes from building too much and getting stuck spending more time on maintenance and bug fixes than expected. It’s like buying too much house and ending up in an underwater mortgage.
The key is to be intentional about what you invest time in and aware of the costs you’re taking on. Err on the side of building too little because you can always build more later. Build things to be easy to throw away and replace; it’ll make your code more modular. Good tech debt has clear, well-known limitations. Document these in code comments, READMEs, FAQs, and conversations with the people who’d care.
Used carefully, good tech debt will help you build software faster by focusing your time on the things that matter most.