The Ten Commandments of Maintaining Legacy Code
Writing new software is fun but a minor part of the software engineering job. Let's talk about the real meat of the job. Legacy code.
Welcome to my newsletter! I'm Dave Anderson, an ex-Amazon Tech Director and GM. I write this newsletter I've called Scarlet Ink, which is a weekly newsletter on tech industry careers and tactical leadership advice.
Free members can read some amount of each article, while paid members can read the full article. For some, part of the article is plenty! But if you'd like to read more, I'd love you to consider becoming a paid member. All of my articles are intended to be evergreen (readable and valid forever). Some weeks I have fresh content; other weeks I'll update/rewrite something from 4+ years ago because I want to keep the quality of all articles high.
Ask any old crusty software engineer what they hate the most, and they'll likely say bad managers. Fair enough. Ask them what they hate second most, and they'll potentially complain about legacy code.
Maintaining legacy code has a bad reputation. Join a team with a new product and an old product, and the difference of working in the two code bases is drastically different.
In the old product, you'll likely find a spaghetti of methods and APIs. Particularly if it was maintained poorly, you might find multiple versions of methods, some of which aren't called anymore. You might find hacked features in places they shouldn't be.
When I joined Facebook, I had to make some small code changes as part of my onboarding process. That was exciting and fun. I decided that my first fix would be a bug in photo albums I'd personally noticed.
I opened the code, found a method that clearly controlled photo albums, something like "photo_album_display", and made a test change. Nothing. That's weird. I looked around, and next to this method, I found a second method named something inspired, like "photo_album_display2". Ok. Fine. I laughed, and figured I'd found the issue. I made a change there. Nothing yet.
I spent more time looking at the call path, trying to figure out how it worked. Finally, in a completely different file, I found a method named something like "photo_album_display_new". Awesome. And yes, changes there worked.
A bit more research showed that those first two methods were not called anywhere in the Facebook codebase. They were just hanging out in the code because the previous engineers hadn't cleaned them up. That cleanup became part of my small bug fix.
I spent most of my career working on legacy code. You will too. In the below article, I'll explain why that is inevitable and what can be done about it. I wrote this article mistakenly in the form of a list. And because I love entertaining myself, I creatively named them commandments. How fun is that? So fun.
Commandment One: Thou shalt not lament nor cry out against the code.
Similar to evolution, code goes through a selection bottleneck.
- When a product doesn't meet customer needs, the code goes away.
- When the code doesn't scale to meet a growing customer base, it changes or goes away.
- When code costs too much to operate, it changes or goes away.
There is one situation in which code becomes legacy code. This is when a product is successful and the code supporting that success was good enough to achieve this level of success.
By definition, this means that instead of legacy code being a sign of failure, it's a sign of success. It means that this combination of product and code delivers enough value to warrant the code being supported as it ages.
"We just took over this system. It's terrible! A technical debt nightmare."
I've frequently heard engineers speak about legacy code in derogatory ways. Unfortunately, they'll suggest that the system is ugly due to the previous engineers being lazy or stupid or not allocating enough time for maintenance.
Your co-workers are humans. They almost certainly had good intentions. Even if you don't say it out loud, by assuming bad things about them (instead of assuming they were busy, overworked, underfunded, or too junior for the scale of the product), you insult them. We shouldn't insult well-intentioned coworkers, should we?
Systems are a reflection of the humans who spent time and energy building them. Insulting a system indirectly insults your co-workers. You wouldn't want someone insulting your hard work. When I was running teams, I regularly reminded people that we don't know the inputs that created our current situation.
You can and should factually recognize issues with your system that you need to address. "This method does not follow the API structure that other methods follow." However, you should firmly avoid and disagree with emotional judgments about the system or workers involved. "Was this API written by an idiot?!" Not ok.
Be a good co-worker, and respect what came before.
Commandment Two: Thou shalt not suffer tight schedules to befoul thy system.
Oh, you say that your team is on a tight schedule?! And it's super important that we hack something into the code just this one time?
Sound familiar? When are people not on a tight schedule?
For various reasons, we often feel pressure to get something done quickly and cheaply. Perhaps it's a marketing emergency to get a feature out quickly. Or we might get some special contractual bonus if it's launched on time. Occasionally our team estimated the work poorly, and we were asked to make up the lost time. Far too frequently, scope has increased through the course of a project.
Regardless of the reason, in 99% of cases, you should not let these tight schedules dictate the quality of your system. In particular, you can't let them dictate that your system will be harder to maintain and harder to update in the future.
Here's what I've repeatedly heard from engineers on my team:
"If we did things properly, that change would take 6 weeks. But I know we don't have enough time for that. If we just hacked it in this once, we could probably do it in 2 weeks."
No! That should not be an option. Don't provide that option. Don't mention that option.
I'm not saying we should ignore that marketing emergency, or bonus, or whatever has made the schedule tight. You can skip features. You can cut other projects. You can launch later. You can pull in more engineers. There are always other options to consider.
What I'm saying is that making your job harder in the future shouldn't be an option. That's a poor investment. We should be making ourselves and our teams and our software better with every change, not worse.
Well, I did say 99% above, right? What about those 1% situations where there's an emergency feature you urgently require because otherwise you'll be sued out of business? Sure, never say never. You immediately schedule "fix the hack" on your team's schedule for next sprint and absolutely don't allow anything to move it.
A big reason to be uncompromising on this is that "just this once" is essentially a joke phrase. Nothing is just this once.