Originally posted on tech.gadventures.com
Technical debt is something that every software company has to deal with at some point. Whether it’s from bad architectural designs or just changing business needs, at some point technical debt will need to be refactored. Here are a few tips I’ve pulled from my experience removing technical debt.
Take Inventory
The first step in dealing with technical debt is to take inventory of the entire codebase. Knowing the extent of the codebase is necessary before you can start removing or rewriting parts of it. Using Google Analytics to understand which parts of the code get used the most can provide an easy jumping off point for web apps. Refactoring the most used portions of the website can often lead to quick wins.
Once you’ve taken inventory of your codebase it’s important to understand how certain functions get used and how they interact with other pieces of the code. If your project has tests, I find that looking through these tests can provide invaluable information. Unit tests are great at seeing the expected inputs to functions and understanding how the code is supposed to be used. If the tests are properly written it can also help you understand what interacts with what.
Taking inventory also gives you a basic checklist that you can use to keep track of what’s left to refactor.
Have a Plan
Now that you have an inventory of everything in your codebase you can start planning the refactor of your technical debt. I like to start this plan with a list of functions that are coupled together. This list gives me an idea of the functions of code that need to be refactored together. From there, I can get an idea of how long each chunk will take.
Once you have the coupled functions you can start thinking about whether the code needs to be refactored. In general, if something works well and still meets the needs of the users it probably doesn’t need to be changed. If it’s no longer relevant, runs poorly or is dangerous from a data integrity standpoint, you should refactor it. Look for any functions that are no longer being used or have been duplicated for different inputs and try to remove or combine them.
Create Realistic Timelines
While you’re creating the plan to deal with the technical debt, always keep in the back of your mind a running tally of how long this will take. Removing technical debt often takes a long time, especially when you do it properly. If you’re refactoring entire sections you may have to consider porting the legacy data over to a new system. This might mean creating scripts that map your old data to your new models or, if the old data models still make sense, just copying the data over.
A big part of removing technical debt is talking to your users. Make sure you understand their needs and how the old code fails to meet those needs. From there, you’ll understand what’s needed from your system and how to handle the refactor. As you’re talking to your users make sure they’re comfortable with the proposed plan. What may work for you in your mind might not work for your users and knowing this beforehand can help lessen the friction of change.
Execute and Launch
Once you’ve determined the technical debt that you need to refactor, come up with a plan to get rid of it, create realistic timelines for dealing with the debt you now have to execute and launch the changes. From past experience, I find that doing small changes and quick launches helps users cope with changes. When you code in a silo and release large functionality all at once there’s a much larger risk of failure. Making sure you have proper test cases for all the different use cases can help mitigate some of the risks of launching large features.
Launching to a small subset of users is a great way to test new functionality and get feedback on the changes before releasing to everyone. This allows you to validate your new code and make sure that it works. It gives you time to create more documentation and make sure that the migration is as seamless as possible.
Once everything’s ready, it’s time to launch to everyone and iterate on those changes.
Preventing Technical Debt
Once you’ve refactored your code, you’re going to want to future proof your codebase. The best way to do this is to make sure you have a comprehensive set of unit tests. Unit tests allow you to deploy your code with confidence, knowing that you’ll catch any regression errors. Along with tests, make sure you use common coding standards to ensure that your code will last. Finally, think about the future of your codebase as you architect new features; it’ll help your code to be more future proof. Taking the time to properly plan your code will make it less prone to becoming technical debt in the future.
Top comments (6)
Our team has inherited a large code base with an similarly large amount of tech debt. While this is rather disheartening, we are using multiple strategies at once:
While it can be argued that this is a waste of time and we should focus on one strategy, this is a great experiment to find out which one prevails - with the added advantage that the reduction of tech dept and the corresponding improvements in performance, security and comfort will reach the customer earlier, even if the whole path takes longer, which allows us to refine our stories on the way.
The boy scout approach is really useful in every day life. Basically everytime we touch an older file we also make sure to create a PR that makes sure it's PEP8. We basically have been doing the same thing. It takes longer overall but it continuously improves the application with less risk of a massive all-in-one rewrite.
When inheriting a code base, the boy scout approach is also a good way to ensure you'll work your way into the code and really make it yours.
Since tech debt is very difficult to avoid, our team found that it is important to document every cut corner and every tetris block that does not quite fit. We do that in our issue tracking software. So technical debt is documented in issues/tasks/workitems that are labeled "tech debt" so it becomes visible for us and the business end of the application.
Tetris ref: twitter.com/richrogersiot/status/7...
This is a great approach! We try to do the same but it's hard to stick with it.
There's two kinds of technical debt. Internal and external.
Internal technical debt is created by the team. For all the excuses we're all familiar with: deadlines, expediency, pressure, rushed, mistakes. The book Clean Code by Robert Martin discusses in depth on how to avoid and correct that kind of technical debt. And paints a clear picture of exactly who to blame: us, the developers.
One of my former bosses had a quote that I love, “If you don’t have the time to do it right, when will you find the time to do it over?” The quote is associated with various people (even though the quote existed before they were born), I'm not sure who deserves attribution.
External technical debt is inflicted upon the application by forces outside of the team's control. Browsers change. C++98 evolves into C++17. Windows 7 evolves into Windows 10. Mac OS 9 becomes Mac OS X. Objective-C gives way to Swift. Best practices in UI of 1995 is clunky by best practices in UI of 2017.
All the points that Adam made apply to both kinds of technical debt. Clean Code can help reduce internal technical debt, and help prevent future debt.
Some may be tempted to say "Hey, I have a great idea! Let's start over! Then we won't have all this terrible technical debt baggage!" I'm vastly more in favor of the "work with what you have; renew, renovate, and refactor from within incrementally" approach. The kind that Adam outlined.
The "big do over" is appealing and tempting, but it is super-risky and expensive. It can work out successfully (I have seen it happen), but it is more likely to go sour (I have seen that happen, too).