Your current software running in production might be considered legacy today,
but it has managed to achieve something remarkable: it solved the problems it
was created for.
As technologies evolve it gets harder and harder for software to survive the
test of time. With exponential progress, software tends to start feeling
outdated much faster than it used to. Analysing the main factors impacting the
longevity of our code and how to improve on it will be crucial to ensure our
solutions will live long and prosper.
Old Technology != Broken Software
Software built with old technologies is commonly considered legacy. It does not
necessarily mean that the code itself is completely broken, though. The usage of
outdated technologies does tend to impact the long-term maintainability of a
project in different ways. It might cause serious hiring issues due to the
inability of finding developers familiar with these technologies, or willing to
work with them. The solution might also need a complete rethink due to the
ubiquitous changes happening in tech all the time.
Nowadays things are really spiralling out of control. There seems to be a
new language, library or framework every day that will be able to solve all of
our problems. Frontend technologies are clearly one extreme, with applications
built only last year now being considered legacy and in need of a replacement.
One of the easiest tricks to ensure your software will live longer is avoiding
to write code tightly coupled to libraries and frameworks. Reducing the number
of dependencies of your codebase is always a no-brainer. Making sure the
majority of the code you write does not rely on a gazillion third-party
libraries should be a priority.
Developers tend to love new shiny libraries and frameworks and don’t think twice
about writing all of their code around them without considering that newer
libraries have an intrinsic higher risk of being abandoned. At the very least
newer libraries will come out with new versions that might require big rewrites
due to the high pace of change in immature technologies.
Try to put an effort in keeping libraries and frameworks away from the code that
provides value for your company. Keep in mind that languages tend to stick
around much longer than libraries and frameworks and use it to your advantage.
Java is one example of a language that has managed to keep up quite well so far,
even though alternatives are popping up everywhere.
Not knowing how the current system is currently supposed to work is another
reason why developers tend to label software as legacy. If the original authors
are not around anymore and documentation is lacking it can be really hard for
newcomers to make changes to the codebase. This usually impacts new feature
release and increases the chances of continuously breaking some parts of the
In the ideal world, you would want to maintain a comprehensive documentation and
the knowledge about the current system by spreading it to more than just single
individuals. Easier said than done.
Lack of automation and tests
The inability to automate builds and deployments has a huge impact and will
increase the friction of trying to keep your code and infrastructure up to date.
In addition, the lack of automated tests might not directly make a codebase
obsolete, but it will definitely make it harder to change. A codebase harder to
change leads to lack of confidence causing dependencies not to be updated when
necessary or code not to be refactored due to the unknown impact of the changes.
High coupling and lack of modularity
Most of the legacy software I’m familiar with has very high coupling between
components. High coupling results in a codebase that is extremely hard to change
causing the same issues outlined above.
Make modularity a first-class citizen. A single module will be much easier
to change than a cross-cutting change across a complex system. Building a
modular codebase allows to isolate future changes, or allow to redesign and
rewrite part of the system without impacting others.
Lack of care
As I wrote about previously, developers need to
care. One of the most
common reasons why software becomes legacy is due to the fact that it has not
been looked after by someone that truly cares about it. Libraries haven’t been
updated. Tests haven’t been written. Developers moved on to new projects working
with different technologies. Not caring leads to software
rot. Authors walking away from
their code is one of the biggest threats to the lifetime of your software,
especially when it has not been carefully planned.
Another extremely dangerous threat is a broken design. A broken design will leak
everywhere, will make it really hard to change things without breaking
everything. It will require an endless amount of workarounds. At some point, you
might want to stop and plan on fixing the root cause of the issues. Re-designing
and re-architecting a broken domain model, or a broken process can be tough, but
it can greatly increase how long your software will be able to exist in
production without requiring a complete rewrite.
One of the reasons why software can end up with a broken design is usually due
to the number of assumptions the developers made when writing the code.
Assumptions should be validated, always.
What is the expected lifetime of your software?
Consider asking the expected lifetime of the software solution you are going to
work on. Sometimes businesses just want to try things out, and would not mind
some throw-away code. In other cases, businesses are in for the very long term
and want to build solid solutions. Make sure you ask the question in order to
not be disappointed if your efforts will go down the drain in a few months, or
if your MVP and all your assumptions are now expected to run in production
Allow your code to evolve
Your complex systems should be built for change, built to evolve. I make the
ability to change one of the highest priorities in the code I write every day.
Requirements are never set in stone, and they are never 100% reliable.
Assumptions, even if validated, might end up being wrong. You might ask the
wrong person, so trust no one. Try to insure yourself for the future and
make sure that if anything does go wrong, you will be able to fall back to a
One of the most important thing that allows you to evolve your software will be
how you handle your data. One of the main reasons why I personally love event
sourcing is how decoupled the data is from the actual code relying on it. It is
also designed to keep all of your data around rather than doing destructive
operations on it, which can be handy when new requirements come along.
It might look like I am promoting extreme future-proofing. As always, common
sense should be applied, otherwise you might end up with heavily over-engineered
solutions. Providing the wrong abstractions will make your code harder to change
instead of making it easier.
Manage your urge to rewrite
I cannot stress this enough, but you should stop rewriting your projects.
You should learn to distinguish working code that does not require immediate
action from what is threatening the maintainability of your system. If
technologies are becoming obsolete, you should plan how to nuke them out of
I see developers jumping onto new frameworks and rewrites every year. The fact
that you hate the code you wrote a year ago doesn’t mean you’re going to love
what you are going to build next. Work on improving your coding skills instead,
not the technologies you are using. If you’ve managed to build a messy codebase
with a simple MVC framework, you’ll probably make an even bigger mess switching
to a reactive one. The same applies with monoliths and microservices. More
modern technologies rarely mean the same developers will end up writing better
Plan an upgrade path
Instead of jumping into a full rewrite by default, try to plan an upgrade path
first. Simple things are easy to rewrite from scratch, but for most complex
applications it just isn’t feasible. If you have millions of lines of code in
your codebase there is no way your teams will be able to rewrite them in one go
without putting the business on pause for years. And you’ll probably end up with
a different kind of legacy anyway, or at least different kind of problems,
Another thing to keep in mind is that a full rewrite will require you to have
a very solid knowledge of what the current system is doing right now, and that
is rarely the case in complex systems.
It’s important to remember that when you start from scratch there is absolutely
no reason to believe that you are going to do a better job than you did the
first time. First of all, you probably don’t even have the same programming team
that worked on version one, so you don’t actually have “more experience”. You’re
just going to make most of the old mistakes again, and introduce some new
problems that weren’t in the original version. — April 6, 2000 — Joel
Respect your legacy
I struggle, at times, to justify calling a codebase “Legacy”*. A codebase
should be considered legacy only if it has been built with now obsolete
technologies or if it’s using now obsolete processes. Anything else is just the
*“Current Working Software” running in production. You can’t call legacy
something you’ve built a couple of years back either, that just means you
haven’t done a really good job at maintaining and evolving it.
Having code you wrote more than a decade ago still running in production is a
great achievement and definitely something to be proud of. If you currently have
projects that no one really knows how they work or no one really wants to work
on take a step back and try to figure out what went wrong. Understanding the
reasons behind it is critical in order to avoid making the same costly mistakes
again. An incentive might need to be in place in order to find someone with the
required skills or willing to take care of what is now widely considered your
legacy code. It might end up costing a lot of time and money, but it will bring
valuable lessons. Next time, make extending the lifetime of your code a