After leaving my PhD in Physics, I’ve learnt to code almost exclusively by building. Inevitably, I’ve had to refactor a lot of code that my past, ignorant self wrote. In other words, I’ve both created and paid off a lot of technical debt.
For a while, this was how I thought about technical debt – extra work resulting from poor decisions and shoddy code. But, as I’ve gained more experience, I’ve developed an appreciation for technical debt. It’s not always a consequence of bad decisions – taking on technical debt can be a strategic move.
Unplanned vs. Planned technical debt
Unplanned technical debt is a result of ignorance. Beginner Russ wasn’t aware of many programming best practices, so he didn’t implement them – creating more work for future Russ. However, even experienced developers can introduce unplanned technical debt. It’s unlikely that you’ll have a full understanding of the context in which your software will be used. Unforeseen changes in the real world can have effects that are impossible to predict. The moral: some amount of unplanned technical debt is inevitable. Some developers even go as far as saying that all code is a form of technical debt.
Planned technical debt comes with an understanding of the trade-offs (e.g. time to market vs. engineering complexity). With new products and features, the value of learning quickly often outweighs the value of writing robust, scalable code. Just like you might take on financial debt to buy a house now and pay it off later, you can take on technical debt to get your software released now and pay it off in the future. One of the important differences with technical debt, however, is that you might not have to pay it off at all.
The MVP as technical debt
New products and features can be treated as experiments. If the experiment fails – if nobody uses the software – then you’ll never have to pay off that technical debt. You can learn your lesson, retire the code, and move on. You’ll end up saving a lot of time by not sinking resources into building software nobody wants.
When we started the mentorship program at SharpestMinds, it was an experiment. We hypothesized that data scientists, both new and experienced, would be interested in income-share mentorships. But we had no idea if it could be a sustainable business. To test it as quickly as possible meant taking on some technical debt. We put together a minimum viable product (MVP) that let us test the hypothesis – saving a lot of programming time by ignoring edge cases and leaving rare events to be dealt with manually.
When we launched the software MVP, we didn’t even have the infrastructure to enable payments. Nobody would be paying anything until they went through a mentorship and landed a job that would trigger the income-share agreement. If we couldn’t get a mentorship started, building the payments infrastructure would have been wasted effort!
When it came time to build software to handle payments, we still left out many edge cases. This made Stripe an excellent choice, since we could handle most edge cases manually through the Stripe dashboard if needed.
How and when to pay that technical debt
As we scale, many of those “rare edge cases” we left out are now happening often enough to cause us headaches. It feels like it’s finally time to pay some of that technical debt – but this is a tough decision to make. Paying off technical debt is, by definition, non value-adding work. It can be hard to justify the time for it. Especially for a small team that is trying to move fast and iterate on product.
At SharpestMinds, we decide on what to build with written pitches. A fantastic bit of process inspired by Shape Up. The pitch outlines the problem to be solved and proposes a solution along with an appetite (how much time are we willing to commit to this?). Pitches are generally used for proposing new features, but committing a development cycle towards technical debt should also involve a written pitch.
Writing out an argument for why a problem needs to be fixed can be illuminating for you and your teammates. Writing forces you to be concrete and specific. What kinds of problems are arising, or will arise, if we don’t pay off this technical debt? How much work, roughly, is involved? Framing this in terms of you and your teammates’ time can help put the problem in perspective and convince everyone that it is worth spending valuable development time on. Perhaps some of them are still manually handling the edge cases and will be eager to automate that away. You may also realize, after writing up the pitch, that the trade-off is not quite justified and that particular fire can be left to burn for a while.
How to prevent technical debt from getting out of hand
Of course, not all technical debt has to involve a long, dedicated chunk of time and focus to pay off. As with financial debt, you can pay it off in small regular payments over a longer time-frame. For certain types of technical debt – say upgrading to a new version of a package, or implementing non-critical improvements like speeding up load times – the timeline may not be urgent. You can save yourself and your team a lot of pain in the future by practicing good software maintenance.
Having good software maintenance habits is a great long-term way to pay off planned technical debt, and it can also reduce the risk of unplanned technical debt. As any software project progresses, all sorts of issues and bugs will arise. Many will be minor and feel safe to ignore for now, but every issue left unchecked is a risk. A habit of keeping those bugs and issues at bay will help future-proof your software.
We employ two main strategies for software maintenance at SharpestMinds, boy scout rule and Fixup-Fridays.
Boy scout rule
Boy scout rule is a reference to leaving the campsite cleaner than you found it. This principle should apply to your code-base. When you’re developing in a part of the code-base and see some shoddy work or something that needs fixing, fix it then and there (ideally, in a separate commit to keep things atomic).
This is a great way to chip away at planned technical debt. For example, this is how we’re migrating to the latest version of React. Instead of refactoring every component in one giant push (a move almost guaranteed to break things), we can refactor the components slowly as we come across them during our regular workflow.
Fixup-Fridays are regularly occurring days dedicated purely to bugs/issues and general improvements. This doesn’t have to be on a Friday, of course – the best time is usually after a wrapping up a big project or feature. We usually work in 2-week cycles at @SharpestMindsAI so this often lands on a Friday, hence the alliteration. The folks at Basecamp endorse a similar practice and recommend having cool-down cycles in between major pushes. It’s good for the code base and it is good for developer morale. It feels good to fix things, and the broken windows effect can have a real effect on the quality of your software.
Fixup-Fridays (or Maintenance Mondays, or cool-down cycles, or whatever name you want to call it) are good for reducing the chances of unplanned technical debt throwing a wrench in your plans. It helps to keep software entropy at bay.