Let’s do a fun exercise. A lot of companies find themselves in a position with legacy code, sometimes decades old, with a recent surge in customers, revenue, and now feature requests. As the new features are implemented, it takes much longer than expected to implement with a lot of introduced bugs. Also, often when bugs are found, it takes a *long* time to debug and fix them. In these cases you will often claim, and probably rightly so, that the team is resource constrained and overworked. How do you solve this problem where things feel out of control?
There are two paths, 1) live with it. Do this if the legacy software is truly gargantuan and still relevant, i.e. most of the code is still used and you have a massive customer base with lots of customization. Joel Spolansky advocates this approach. The other option is 2) refactor the code. Do this when you have potential of losing market leadership because of the inability to keep up with competitors. This is the approach I generally prefer and which I will address below.
To start, clearly identify why bugs are introduced with changes to the code. For legacy code, usually it’s because of classic coding issues, most notably where dependencies exist between modules that should be independent, referred to as spaghetti code. The spaghetti code can be exacerbated by difficult-to-change interfaces, say with a server or embedded device to which your code communicates, i.e. to fix issues, not only must your code be fixed, but that other code owned by another team (or even company) must be updated. So where to begin actually solving the problem?
To fix this issue, start by creating ownership of portions/modules of code. Do not create a team where everyone owns everything and nobody is responsible for anything. With ownership you create responsibility and accountability. Second, perform any quick fixes that drastically improve (aka low hanging fruit) debugability of the code. This will allow the team to continue fixing the critical items that are needed to keep the business in business. Third, prioritize the refactoring of the code. Do not try to obtain approval from management to set aside time to refactor the code base. In my opinion, this need not be “approved”, it should be part of your job in managing the team’s workload. Fourth, start defining the interfaces of these modules – both the current interface and what the interface *should* look like. This is probably the most important part of refactoring – it doesn’t have to be perfect but you’ll find refactoring fruitless if you do not have a defined interface. Fifth, create axioms. See my blog post on axioms for details. Sixth, with a clearly defined interface, consider writing test cases. However, do not over-emphasize this part. Test cases are great but it’s easy to get caught up spending too much time here. It may be good to time box this chore. Seventh, de-emphasize the current list of bugs and prioritize the refactoring effort. The refactoring should as a consequence fix almost all the outstanding bugs and should include several new features. This resolution of bugs and addition of new features is also why you need not receive management approval for refactoring, instead emphasize these real user-facing benefits of the refactoring. Eighth, create a culture of no fear. There will be a period of time where nothing seems to work and large swaths of features are broken. Fantastic. Part of the process of breaking things is learning. That area of legacy code that nobody wants to touch because nobody understand it and updates to it always creates bugs, well, now you have somebody who fully understands it. That’s progress. Ninth, keep QA actively testing. With a highly available code base and tight coupling between developers and QA (more details on my QA blog), bugs will be caught early! Tenth, get to work. Do not over-emphasize planning. I’ve seen teams produce bottom-up refactoring of the entire code base, in beautifully documented, planned-in-stages, power point slides and UML flowcharts. It can look impressive – everybody likes a well planned venture, especially one that involves such drastic change. However, the team is inadvertently performing the waterfall approach. These ten steps are an outline of how to approach refactoring.
Sure, the refactoring that I have described is simplified. Also, what I have outlined is hypothetical and common sense and specific issues must be addressed. But it’s good to have a simple, yet complete, plan of attack. Emphasis on simple, as described in step 10. Refactoring has huge benefits. A cleaner code base *drastically* improves productivity. There are fewer bugs. When bugs are found, they are fixed faster. Features are introduced faster and with fewer side effects. Then there are the additional benefits: engineers are happier. Engineers much prefer to work in clean code where they are more productive. It requires a team effort, a culture of no fear, a lot of hard work, but the benefits are enormous.