One of my managers many years ago was in a meeting where someone from sales was disparaging our legacy code and he made a comment that has stayed with me for decades: “Legacy? Oh you mean the code that pays our paychecks?”
The legacy system pays our paychecks
Most code that hangs around long enough to be legacy is there because it serves a critical purpose. Many times that software is core to the business. Some times it’s just too useful to abandon. In most cases, no one understands it well enough to be sure that it could be rewritten or replaced.
If this legacy software were to go away, the business would struggle to replace it.
Cruft as scar tissue
Much of the cruft on a legacy system is scar tissue from lessons learned. Every time a legacy system is changed, there’s a little bit of code that no longer matches the original design. Maybe it’s just a conditional to handle an edge case the caused a bug. Maybe the business changed a little and we had to twist the original logic a bit.
These are not necessarily mistakes in the original design. They are lessons earlier programmers learned about this important system. Some of them were requirements that were learned along the way, but that no one knew when the project began. Some match changes in the operational environment or legal environment or direction of the business.
There’s no silver bullet
Read the Mythical Man-Month until you understand this. There is no one, true way to design or write a significant system. Any design will be based on the understanding of the programmers at the time, the language, environment, time constraints, business needs, and a thousand other pieces of information. Given all of those competing requirements, every design is the result of trade-offs. No tool, language, methodology, library, or snake oil promised by some slick salesman will make the process trivial.
Most importantly, none of these can be applied to make replacing or even fixing a legacy system easy or guaranteed.
Developers did the best job possible
The previous developers did the best job possible with the knowledge and resources they had at the time. Much of what they needed to know came from the process of building the system. If their initial design was close enough to the final requirements, the result might look clean.
Unfortunately, when anyone begins a design and starts the implementation, they know the least about the problem that they ever will. Given what they knew and the tools and knowledge they had, they did the best job they (or anyone else) could have done.
Our knowledge is imperfect
Our knowledge is still imperfect and we probably won’t do a 10x better job. Any time someone looks at legacy code and sees a mess that should just be thrown away, they are admitting that they don’t know everything that it does. If you don’t know what it does, you’re in much the same situation as the ones who wrote it originally.
If you start with this less-than-perfect understanding and try to rewrite the system, you will again learn as you write and the result will probably not be a whole lot better than the original system. Except this version does not contain all of that knowledge that makes the current legacy system work as well as it does in the current environment. So, we begin the process of generating a new set of scar tissue while learning how the system needs to change.