Legacy is an overused toxic word in Software Engineering. In this essay I argue the problem is not software, it’s people, as usual. What is legacy is not the code, but the way we look at it, and that breeds helplessness and costly rewrites. There is another way!
How Code Becomes Legacy
People call some code legacy when they are not happy with it. Usually it simply means they did not write it, so they don’t understand it and don’t feel safe changing it. Sometimes it also means the code has low quality1 or uses obsolete technologies. Interestingly, in most cases the legacy label is about the people who assign it, not the code it labels. That is, if the original authors were still around the code would not be considered legacy at all.
This model allows us to deduce the factors that encourage or prevent some code from becoming legacy:
The longer are programmer’s tenure the less code will become legacy, since authors will be around to appreciate and maintain it.
The more code is well architected2, clear and documented3 the less it will become legacy, since there is a higher chance the author can transfer it to a new owner successfully.
The more the company uses pair programming, code reviews, and other knowledge transfer techniques, the less code will become legacy, as people other than the author will have knowledge about it.
The more the company grows junior engineers the less code will become legacy, since the best way to grow juniors is to hand them ownership of components.
The more a company uses simple standard technologies, the less likely code will become legacy, since knowledge about them will be widespread in the organization. Ironically if you define innovation as adopting new technologies, the more a team innovates the more legacy it will have. Every time it adopts a new technology, either it won’t work, and the attempt will become legacy, or it will succeed, and the old systems will.
The reason legacy code is so prevalent is that most teams are not good enough at all of the above to avoid it, but maybe you can be.
Be a Good Engineer, Don’t Create Legacy
If we don’t want to clean up after other people’s messes, it’s only reasonable we don’t create messes for others.
Don’t Write Crappy Code
I’ll define crappy code as code that cannot be transferred to a new owner and has to be rewritten instead. The main reason it happens is that the code is not clear enough. That comes from simple incompetence, like poor architecture, or lack of tests, but also from counter-productive fashions that developers apply mindlessly. The two most damaging ones are the notion that good code has no comments, and that it needs to use fancy technologies, like micro-services or verbose and complex architectural patterns.
The heuristic I like to use to avoid shooting myself in the foot is asking “what is the simplest thing that could work?”. Then I only complicate that to solve actual problems. It’s trivial to shoot down a bad idea like a premature adoption of micro-services with a simple question: “What problem does that solve?”. No good answer, no work. And no, “I’d like to learn about micro-services” is not a valid problem to solve4.
Don’t “Innovate”
A lot of engineers think that innovation is adopting modern technologies, like new frameworks and languages. That mistaken belief generates lots of legacy, since once we start writing new services in Golang, all the existing ones written in Javascript become legacy.
Instead, I define innovation as solving new problems or solving old problems in a better way. If you adopt a new technology that doesn’t do that, I don’t call it innovation, I call it playing with other people’s money. It’s fake “innovation” that creates legacy. It’s work that instead of solving problems creates new ones. What a waste!
Notice I’m not saying you can never adopt a new language. I’m just saying it must solve an actual problem and should be the best way to solve it. For example, if you are spending millions of dollars to train a machine learning model in Python, it may well be worth it to rewrite it in Rust to save money.
Teach Others
As I said before, the most common reason code becomes legacy is that the creator moves on and the new maintainer can’t understand it. Writing good code can certainly help, but teaching the new guy is even better. Honestly, most professional programmers can’t code well, but they all can teach a coworker about how some of their code works. Teaching is not only good for the learner but also for the teacher. You will find that explaining yourself will make you reflect and become a better engineer. Finally, mentoring is a core skill of Senior+ programmers, so developing it will be good for your own career.
The best form of teaching is pairing on code. It’s amazing what a newbie can learn in a 2h pairing session to fix a hard bug. It is even better to pair on new code, as ownership will be shared from the start.
The second best is code review, which works less well because it’s slower, async, and requires written communication which people tend to be worse at. Finally, it requires a nitpicky reviewer with good taste, and those are rare qualities.
Leave Honorably
A good engineer does not only build good systems but also enduring ones. They make sure systems are taken care of, even after they leave. They do it by teaching others continuously, but especially as they plan to leave. For example, they give ample notice (one month or more) and use that time to ramp-up others and write documentation.
Fix Other People’s Legacy
As much as we don’t create legacy, we are bound to find it our careers, so we need to know how to best manage it.
Not Everything is Legacy
I would only use the word legacy for systems that we don’t understand and that are hard to change. If a system is merely old, but is working fine, using that word brings undue stigma that might encourage misguided rewrites5.
If we are actively developing a system we need no word for it. If it’s development is slow or stopped, we can say it’s in maintenance mode.
Delegacify
Since the problem with legacy code is that the maintainer does not understand it, you can delegacify6 by simply learning it. You will still find it crappy7, but you will be able to improve it overtime, to add new features, to add tests. This might seem less glamorous than a full rewrite using the hot new thing, but it’s the right thing for the company, and if we consistently do the right thing, we’ll grow a reputation and be rewarded for it.
In rare cases legacy systems are truly unsalvageable and should be thrown out, but you can only be sure after you deeply understand the system and its problems. That is, you need to try to delegacify a system for several months before you can credibly decide a rewrite is the best option.
One good heuristic to decide on a rewrite is whether we strongly believe that fixing the system will take much more effort than rewriting it. Notice we can’t be anywhere close to certain of that unless we deeply understand the current system and what it would cost to write a new one.
Face it or Run
Someone must fix the legacy code, but it doesn’t have to be you. It’s far more honorable to switch projects or companies than to lead a misguided rewrite.
If you stick around, you might feel it will be a lot of pain with no end in sight, but you will be surprised how much better it can be after a year. Also, you can involve more Junior folks and transfer ownership to them. Work that is boring and thankless for you might be an exciting opportunity for a Junior engineer.
In Conclusion
Most companies are full of legacy systems. Incompetent programmers create them and leave the mess for the next person. That lucky fellow then lacks the moral fiber to fix the mess, and so they rewrite everything and the cycle starts anew.
You can break the cycle; you can take care not to create legacy systems and delegacify the ones you find. You will be a better engineer for it, and the market will recognize you as someone that solves problems rather than creates them. Your reputation will grow, and you will be handsomely rewarded.
For example, it does not have tests or is structured poorly.
It’s tempting to understand “well architected” as using the latest and greatest design pattern, like micro-services, clean architecture, or event driven architecture. In practice it’s the opposite. Code become legacy when knowledge about it can't be successfully transferred to a new person. Simplicity is what allows that granger, not the complexity of a fancy architecture.
I’m not saying architecture patterns should not be used, I’m only saying that to the extent they increase complexity they encourage legacy creation.
Yes, that means code should have comments. Or more precisely it should be self-explanatory, which in practice means it will have plenty of comments on the whys behind the code. See my essay “Good comment, Bad comment” for a discussion of this topic.
“I’d like to learn about micro-services” is a valid problem overall, just not one that a professional should solve on the company dime. We can always learn in our own free time. It might seem foolish to put the company needs ahead of our own, but if we become expert problem solvers rather than legacy creators, the market will reward us, not only with money but with opportunities to learn interesting and useful technologies.
The classic reference on the foolishness of rewrites is the blog post “Things You Should Never Do, Part I” written by Joel Spolsky all the way back in 2000.
Yes, I just made up that word. It means changing our judgement of some legacy code tono longer consider it legacy.
A bad engineer thinks all code they didn’t write is garbage. A good one knows their old code was bad too—but not this time. This time, they’ve nailed it.