With our 5.0 release out the door, the development team here at Apprenda has had the chance lately to go back and pay down some technical debt.
For those unfamiliar with the concept, technical debts in software are those places where, while the code functions properly, it has internal problems that make it challenging to maintain. Like financial debt, technical debt is typically created because we need to do something today, but can’t afford the full cost. Also like financial debt, you pay interest. That interest is incurred when it takes longer than it should to implement a new feature or fix a bug and the debt creates a “bug farm” with increasingly complex bugs developing.
At Apprenda, everyone from our CEO down understands that some level of technical debt is to be expected and can even be a useful tool, but sometimes you have to pay your old debts in order to move forward. For the rest of this post, I’ll talk about how we’ve been doing just that.
Deciding What Debt to Pay Off
Before we could start paying down debt, we had to decide which debt we should pay. To do this, we started taking inventory of our debt by considering a number of questions. Where do we have bug farms? Where do we have something that feels more complicated than it should be? Where does our actual architecture not match our desired architecture?
Once we had an inventory, we filtered it against a few more questions to get to a list of debt that we could realistically pay. Is it accidental complexity or is it inherent to the problem? Is the debt something that can be paid off iteratively as we’re working on product features? Is there a clear path from where we are now to where we want to be?
Finally, we took our trimmed down list and put it in priority order. Prioritization is probably the most touchy-feely part of the process, but there are a number of things that go into it. How bad is the problem? How much effort is required to fix it? How well can we explain to product management what we’ll be doing and why? Since we still wanted to follow our development process, we took our top priority items and wrote stories for them, just like we would for any product feature and added them to the queue.
Doing the Work
Actually paying off the debt is the part of the process that’s the most fun, but I’m not going to spend much time explaining it. Books have already been written on how to refactor and improve your code, so I’m not going to attempt to go anywhere near that level of detail.
What I do want to stress is how important our automated testing and the skills of our test team is to this process. Without established top-quality testing, there’s no way we could make changes like this without breaking things that used to work. As you can imagine, if I needed to tell product management: “I want to make my life easier without exposing any new functionality to our customers. And by the way, I’ll probably break the features our customers already have in the process,” that wouldn’t go over so well. However, because of the automated testing we have in place and the confidence we have in our test team, we’re able to perform this work with very limited risk. This is essential to performing the work at all.
Making Sure it Happens
While I think management in most organizations would agree that paying down technical debt is a good thing, devs often have trouble actually getting the time to do it. So how did we pull it off? First, I want to point out again that Apprenda’s management has always been very supportive of these efforts. Yet in the past we have still struggled to effectively pay off debt without sneaking it into the work for a new feature. While this strategy works well in many cases, sometimes the debt is just too big to say, “…and since we’re going to be in there anyway, we’ll also take care of this little thing over here.”
In the past, we’ve tried tactics such as reserving a portion of the available development time toward alleviating technical debt when product management is planning what we would do for the next release or decreasing our velocity number. This most often results in that extra time being used for some extra feature we needed to include in the next release. The key for us was recognizing that, while everyone has the best intentions, the only way to make sure that product management didn’t take away the technical debt time was to do the work first.
This simple adjustment means that, as I write this, we’re finishing up the work to remove our worst bug farm. This makes a portion of our codebase that had previously had everyone concerned into a something much simpler and easier to work with. Across the product, on all of our technology stacks, we have fixes like this happening and the result will be improved stability and faster iteration of features for all of our customers.
As a developer, nothing feels better than tackling a piece of long-standing technical debt and turning code that everyone hates into code that’s a joy to work with. That being said, it’s important to make sure the right debt is tackled. Be sure to have a testing strategy to prevent regression and actually carve out the time to do the work. My hope is that by talking about how we’ve tackled these issues at Apprenda, we can help others to successfully tackle them within their own organizations.