A Manager’s Guide to Legacy Code
Editorial Note: I originally wrote this post for the NDepend blog. Go check out the original here, at their site. If you like posts about static analysis, code quality, and architecture, head on over and check it out.
If you have a sadistic streak and manage a team of software developers, it’s probably high entertainment to dredge up some old, dusty piece of software and then to task them with maintaining it. If, on the other hand, you’re a normal human being and you’re asking this because it’s necessary for your business, you brace yourself. After all, this is legacy software, and the reaction of the team is likely to be quite predictable.
Alright, let’s take a look at this thing. Oh, man, look at that right there. A global variable. And — oh my god — there are dozens of these things. Who even wrote this? And, look at this over here. That’s the kind of idiotic, backward code that we used to have to write 20 years and 6 language versions ago when this code was current. But even when it was current, this code was horrible. This was obviously written by a trained ape.
When you’re a developer, the only thing worse and more contemptible than the uninformed code you wrote years ago, is the code that someone else wrote years ago. Not only is it alien to you in makeup and reasoning, this legacy code also features patterns that have gone out of date or even been forgotten.
But lest you, as a manager, assume that this is simply a matter of developers being prima donnas, consider that an encounter with legacy code bother developers precisely because it renders them less effective. They’re professionals, wanting to do good work, and the lead balloon you’ve dropped in their lap is an impediment to that.
Legacy code is code that has been humming along, for years, in production, often largely untouched and abiding by some uneasy truce with its users. It’ll keep half-working as long as you don’t go poking around too much. When you ask a developer to dust it off and start looking at it, it takes time to peel back the layers of the onion and arrive at understanding. And that’s time not spent coding. And that’s just the beginning.
Once understanding is finally reached, the devs are often being asked to do something that violates the original intentions of the system’s authors, meaning that they’re now going to have to play a high stakes, thankless game of Jenga. And, by the way, they’re going to have to leave behind all of the progress made in programming technologies and patterns in recent years as they do so. It’s easy to forget that the programming languages themselves acquire helpful features over the course of time, allowing you to write in one line what once would have taken 10 or even 100 lines.
Now imagine life for the developer. You’re asking this person to leave behind modern tooling, to work on a fragile product, populated with mental land mines, and you’re asking them to do so without the benefit of the tools that allow them to do their job effectively. And, by the way, you’re also asking them to do something that will in no way benefit their careers, unless you give them a better title for their trouble. After all, “thanklessly cobbled features onto WheezeBot 1.6 using Classic ASP” is hardly an attractive resume bullet point.
What To Do?
I mention all of this because it’s important to understand the terrain if you’re in a position where you need to tackle a legacy code base. Maybe you’re making some kind of sweeping modernization play on your software, or maybe you just need to add a series of one-off features to some long tenured internal application. Perhaps your flagship, mostly-modern software, has a legacy core that you have to touch from time to time.
Whatever the case may be, if you need to deal with legacy software, it’s important to be deliberate and strategic in how you handle this. As with any personnel management concerns, some approaches will be a lot more effective than others.
First of all, resist the urge to encourage specialization within the team. That seems counter-intuitive, since part of personnel management is figuring out how to play to people’s strengths. But what you really don’t want to do is have Alice handle all of the green field stuff, Bob handle all of the .NET-flavored legacy stuff, and Carol in charge of the really ancient Cobol stuff. This is locally maximizing.
Sure, on any given day, each of them will be most effective working in his or her purview, but if one of them, particularly the legacy folks, leave the company or even go on vacation, things can get ugly. You need some knowledge redundancy, especially with the legacy portions of your portfolio. It’s easy enough to hire another ASP MVC programmer to work on some CRUD app, but good luck finding another Bob or Carol to work on tribal-knowledge filled, sunset technologies.
Rotate Your People
Probably the best way to achieve knowledge redundancy is to rotate your people. Have them pair on different tasks, and assign them to different parts of your set of applications at different times. This isn’t to say that everyone has to spend an equal amount of time everywhere, but you want to achieve some cross-pollination.
Another important benefit of this practice is preservation of morale. If Alice always gets green field assignments, and Bob is always stuck on the legacy stuff, the frustration I described earlier in the post may set in on a permanent basis. And with protracted frustration and the impression of career stagnation comes a tendency to want to look elsewhere. The last thing you want is to isolate knowledge to a team member who is growing increasingly disgruntled, even if, for some reason, you found attrition to be an acceptable state of affairs.
With legacy code, it’s also important to manage expectations — everyone’s expectations. With the team, don’t gloss over the fact that everyone is going to have to pitch in from time to time in the legacy mess. Be clear about what’s involved so as not to create resentment.
But just as important is to manage external expectations, particularly if you’re starting to engage a comparably slow cross-pollination process. When you have creaky old code, it’s important to be clear to all stakeholders that features will take longer and carry more risk than with the applications that they’re used to. Yes, you know that Alice can add a combo box to a page in the MVC app every 15 minutes, but that doesn’t mean that she or Carol or anyone else can do the same on the green screen application. Archaeological expeditions are slower going and require a lot more tender loving care.
Have an End In Sight
The last piece of advice that I’ll offer when it comes to handling the legacy stuff is to have some kind of end game. Is this a unique, one time venture into the dustbin of code history? If so, how do you know another and another won’t be required? The most critical thing is to avoid a period of unending legacy support.
If it’s truly a one-off, you should gain real clarity on the parameters under which future changes may be necessary, so that you can assess their likelihood. You should also establish the criteria that will make the legacy project a total and under which you will abandon it.
If a lot of maintenance is going to be required to some legacy thing, then look for a different kind of end game: modernization. Can you carve out some time with each addition to bring it along to a more sustainable framework? Can you slowly retire the really old parts? How are you going to move to a better state?
And, finally, think about the future and how you can avoid writing tomorrow’s legacy code today. Legacy code is code that you’re afraid to touch. Encourage flexible, extensible designs, and, for goodness sake, create an automated test suite that will let future maintainers know if they’re breaking anything. If you do this, your codebases will evolve with the time, and you won’t be looking at blogs for advice on dealing with legacy code.