Over the years, I have seen many companies struggling with paying off technical debt and legacy code. Heck, I produced legacy code within a 45-minute session at a code retreat on my own, so consider me guilty as charged as well. Over the years, I have seen a pretty tiny fraction of companies actually managing their technical debt. So, here are a few stories that I oftentimes share from these companies and how they tackled technical debt and legacy code – with no claim for this to be a complete list of things that might work. Please add any additional advice you want to share in the comments.
The Boy Scout Rule
My go-to advice stems from the boy scout rule:
Leave the campgrounds cleaner than you found it.
In your team develop the attitude that whenever you implement something new, the pieces of code you touch will only be checked in afterward in a slightly cleaner state than you found it. Over time, the pieces of code that you touched regularly will end up cleaner, only leaving those pesky places that you rarely touch, if at all, in an unclean state.
Over time, you will end up in an ever slightly better position than the day or check-in before. At one point you will realize that the code base you’re working on, now became quite manageable, and can maybe move on. But please, keep the boy scout rule in place. Overwise technical debt will keep reappearing.
We established this rule on a team that I joined years back. They worked on an old Java code base, 10 to 15 years old with maybe 1 million lines of production code. There were zero automated tests when I joined the team in that year in August, and they had some features that needed to go live by the end of the year due to some changes in laws surrounding their business.
With every product backlog item we implemented, we considered adding automated, micro-level unit tests for the new lines of code that we wrote. Within a period of four months, we managed to reach a code coverage level of 10-15 percent, depending on the component of their system. By the end of the year, I noticed less defensive programming, and in most parts, just by the click of a button or by running the build script, we received feedback from the newly added unit tests on whether we broke something that had worked in the past. All of that enabled us to work more effectively with the code base, sometimes daring larger steps or more interesting re-designs of the existing code – and stepping back when unintentionally our unit tests told us seconds after a change that we broke something.
All of this was enabled by applying the boy scout rule and adding unit tests as we went ahead.
The Big Redesign in the sky
Of course, in that old code base, our efforts did not solve every problem. Over the years and decades, so much technical debt had accrued. I recall in the early years of software craftsmanship, I recall a blog entry from Uncle Bob Martin about the “Big Redesign in the sky”. Unfortunately, I’m unable to find a direct link to the original from back in the day. So, here’s the essence retold from my old brain’s memories.
A software system is so messed up in technical debt, that no programmer dares to make even the slightest change without cautious steps. Pretty soon, every programmer calls for a big redesign of the system, the company is eventually convinced to go ahead with writing the system anew and a tiger team is formed to tackle the new endeavor. Some programmers are left behind to maintain the old system while the elite tiger team writes a new system for some while. The tiger team tries to put in all the lessons learned, but the business gets more nervous and starts to ask for new features in the old system. The tiger team continues to work on the new system, being pressed to re-implement the old system, not daring to work on the new features in their new system, yet. Over time, many new functionalities get added to the old system that is not implemented in the new system. When the new system is finally rolled out – probably later than initially thought – the tiger team hands it over to the other developers that now need to put in the new functionality as well. Pretty soon, new calls for a redesign pop up restarting the cycle again.
In our case, after the first initial four months of me adding automated unit tests to features we developed anyways, the team was split into two teams: One team working on new functionalities, the other team working on refactoring the system. Both teams worked on the same subversion (yeah, I’m pretty old) branch. Whenever the refactoring team touched similar pieces of the code base where the other team added new functionality, they coordinated with each other. In that year with the split team, they managed to roll out 25 releases – every other week, in line with their 2-week Sprint cadence – to its 400 subsidiaries around the country. I oftentimes compare this situation to changing the tires of a car that is underway on a German highway at full speed.
Basically what they did was lay out the big redesign as a series of many refactoring steps. In this 10 to 15 years old code base, there were many questionable things. For example, early on they invested in offline functionality for the branch offices. Regularly they send over their daily tally information to the piece of the system that was running in its company headquarters. Since the branch offices did not have reliable internet connection all the time, early on they invested in hand-written offline functionality. Over time, more and more libraries added some kind of offline functionality so that now many lines of code could be replaced with the magic of some library. Of course, replacing the old code that was proven to work in 400 branch offices was risky, so they split up the refactoring of that piece of code into smaller, deliverable steps, that they could still ship at the end of every two-week Sprint.
Working in this way of course took longer for the whole redesign to eventually finish. I think it took more than 15 months in the end. Considering the alternative to work with the full force of all team members on the refactoring – say – for 4 months, and all the refactored system can do then, is basically the same as before, is not a valid option I have seen that many companies can dare to do. In this circumstance, they were heavily driven by changes in laws and regulatory changes that turned up two, maybe three months before they needed to be implemented in production. So, this was no valid option this company could use.
Of course, being able to split larger refactorings into a series of smaller ones that maintain the ability to still ship the product after each step, is a skill that many teams lack. This company was lucky to have experiences developers to pull this off. So, educating your developers about refactorings, and how larger refactorings are merely a series of smaller refactorings was something the developers in this company knew by heart.
Refactoring to patterns
In a story I shared recently, I surprised a self-proclaimed architect at a local coding dojo challenge. Basically, what I applied there, were the lessons I took from Joshua Kerievsky’s Refactoring to patterns book where he stressed the point I just raised. Combining Design Patterns and Refactorings to go from a Strategy pattern towards a class hierarchy and vice versa by applying multiple refactorings in a certain order, is a skill that not many developers have learned.
Basically learning the basics of our trade as software developers, not taking the first stack overflow solution for granted without understanding and rewriting it to fit your needs, is a habit, I continue to this day when working with code. I continue to be surprised, even years after such insightful books and articles have been written, that there are so many developers still convinced on how they can change the world without knowing some of the basics behind that.
Three basics
These are my three best advice when dealing with technical debt and legacy code:
- honor the boy scout rule
- split up larger refactoring into smaller steps
- learn to walk before you run
What is your best advice on technical debt and legacy code that you feel you have a war story to share about? Anything obvious that I missed along the way?
One thought on “How to handle technical debt?”