Ah, legacy code. Two words that can strike fear in the heart of any developer.
Two words that if mentioned in front of your developer friends will immediately cause grimaces and sighs. One of them - a battle hardened veteran puts their hand on your shoulder and tells you to pull through until you get the chance to rewrite.
Therapist: “What’s the most traumatic event in your life?” Developer: “I’ve worked on a legacy system”
Why is the general opinion about legacy software negative? Is it so bad to work on a project which is 5 to 10 years old?
Is the problem with legacy code only related to the age of the software? Perhaps the problem is more related to the technologies used? Can a 2 year old project cause the same desire in us to turn it off and rewrite it anew?
The software field is dynamic, it changes fast and provides a good challenge for anyone that enjoys learning. But every coin has two sides.
New technologies bring a lot of excitement but draw it away from older software. Finding better way to structure applications or design systems shows the faults in the older ones.
Why is the software industry so disgusted by legacy code? Why aren’t other fields so obsessed about progress as well? My dentist has been using pretty much the same technology ever since I can remember.
We’ve probably found better ways to build houses as well. Yet we don’t have the need to rebuild them every few years.
Me: “But I like my house!”
Engineer: “We found a better way to build it. Tear it down boys!”
What Does Legacy Even Mean?
The term legacy system means an old or outdated method, technology or program which is still in use. It may be in need of replacement but most importantly - it is still running in production.
The first time the term legacy was used was some time in the 80s. There needed to be some distinction between previous software created in the 70s - hence the term was coined.
Legacy systems are old - that’s implied by the name. But we don’t frown upon such systems only because of their age. Some legacy systems are stable and mature despite that. There are many old technologies that are actively maintained, tested and improved upon.
However not many old software products manage to withstand the test of time. While it is still functional - the biggest problem with such software is that it uses designs and techniques which may now be considered obsolete.
In other words - the stack is not one that we’d use to start a new project in today.
Often such systems are not well maintained and fall victim to software rot. If not kept up to date they fall behind the fast iterations in the software development world and become unappealing to work on.
The software may depend on libraries or tools which are no longer maintained, so fixing problems related to them becomes troublesome.
Especially in large legacy codebases it’s hard to get an understanding of the dependencies and connections between components. So, making changes can be a risky undertaking.
Can we be sure that making a small modification to a method is not a decision that will haunt us at 3am on a Saturday?
Developer: “How do we test if this works?”
Legacy Code Veteran: “We ship it to the customers and pray”
If the code is extensible we can build upon it and rely on the foundations. If it’s not - making modifications becomes a delicate process.
Testing Legacy Code
But this is what tests are for, I hear you say. This is right - the sole purpose of tests is to give us confidence that we are shipping working code. Unfortunately, most inherited projects written a long time ago lack a proper test suite.
Legacy code is code without tests.
— Michael Feathers, author of “Working Effectively with Legacy Code”
The book Working Effectively with Legacy Code focuses on refactoring techniques and methodologies to make legacy code easier to work with. However, the core idea in the book is the importance of tests.
Before we make any changes to legacy code we should write tests for it. Tests that could validate the proper output and functionality of that class or module. Then after every change we can validate if we have achieved the correct behaviour or we have broken something.
If a project was written 2 years ago but is tightly coupled and lacks proper testing then it might as well be considered legacy too.
This leads us to a conclusion - the problem with legacy code is not the lack of tests per se. It’s the insecurity and discomfort of making changes to it that the absence of tests brings.
The Legacy Lives On
If legacy systems are such a problem can’t we pull the plug on them? The reasons why legacy code continues to live go beyond its technical quality.
The software may still be bringing enough value for customers as it is. The reasons may also be budgetary - it’s often cheaper to maintain old software rather than rewrite it.
If a legacy codebase requires only minor changes here and there then a rewrite wouldn’t be justified. Another reason would be the lack of better options technology wise.
Can We Rewrite It?
With all that said, we’d much rather replace the legacy codebase than work on it. So the most common question when discussing the future of such systems is whether they can be rewritten.
Developer: “Why don’t we just rewrite it from scratch?”
Manager: “Oh, here we go…”
As engineers we don’t want to deal with old and neglected systems partially because we believe we’d do a better job than the people who built them. We hate going on safaris through miles long classes without a single comment.
This is not the kind of work that one person writes and leaves behind. It’s code that many people worked on through time. Each with their own style, preferences and opinions.
But it’s easy to see that the decisions are subpar now that we have the complete picture. That legacy system took years to build. The engineers before us had to make decisions based on budget, conflicting requirements and stressful deadlines.
We have no proof that we would do a better job than the engineers before us if we are to work in the same conditions. We also have no proof that we’d do a better job at rewriting the system if we start now.
It’s frighteningly easy to see possibilities for improvements when we are new to a project. A class doesn’t seem to be used. A function makes no sense. A mess of conditional checks looks like it can be rewritten in a single line.
New Team Member: “I deleted a method which wasn’t referenced anywhere in the codebase”
Veteran: “No! That method gets dynamically called by a cron job that runs once every quarter!”
There’s a mental model named after that - The Fallacy of Chesterson’s fence.
Chesterson’s fence is a principle that says we should never take a fence down until we know the reason why it is put up.
As an engineer you notice some code that seems redundant or too complex - “This is nuts, I can’t see why it is done that way”.
You strongly believe that you can rewrite the whole thing in a couple of lines.
To which a more senior member of the team would say - “Show me why it’s done like this and then I’ll let you rewrite it”.
There are many factors that contribute to the negative stigma of legacy software - age, code quality and the lack of tests are the biggest ones.
Projects which are not maintained and improved easily becomes victim of software rot.
Such software is hard to work on for a variety of reasons - tight coupling, lack of documentation, high risk of failure.
Rewriting seems to be the silver bullet but not always.
We have no guarantee that we wouldn’t fall into the same pitfalls that the previous teams did.