Legacy rescue · Guide
Rewrite or rescue? What to do with software nobody wants to touch
If last week's PHP vulnerability meant an urgent upgrade rather than a routine one, this article is for you.Most businesses that have run software for a few years have at least one system like it. It works. Mostly, but it is running on an old version of PHP or Laravel. The original developer has moved on. Nothing is documented. Every change takes longer than it should, and every release comes with a quiet fear that something else will break. And at some point, someone in the room says:
“Nobody wants to touch this codebase.”
That is usually when the conversation turns to a rewrite. Start again. Do it properly this time. Modern stack, clean code, no baggage. It is an understandable instinct. In most cases, it is the wrong one.
Why a rewrite feels like the answer
A rewrite is attractive because it promises a clean break. No more working around decisions somebody made eight years ago. No more fragile code that nobody understands. No more upgrade path that looks like a cliff edge. Developers often like the idea too. Building something new is more enjoyable than
untangling something old.
A rewrite asks the business to do something very risky. Stop improving the system you depend on, spend months rebuilding what you already
have, and hope the new version does everything the old one did.
Why rewrites so often go wrong
The old system usually knows more than anyone thinks. Years of bug fixes, edge cases and business rules are buried in that code. Some are
documented. Most are not. Many of them exist because something went wrong once, a customer complained, or an
accountant asked for a report to work in a particular way.
A rewrite has to rediscover all of that. Usually the hard way, in production. The other problem is time. While the new system is being built, the old one still has to run. It still needs security fixes. The business still needs changes. So you end up maintaining two systems, or freezing the old one and asking the business to wait.
Rewrites also tend to be estimated against what the team can see, not what the old system actually does. That is why they so often run late, run over budget, or launch missing features that people quietly relied on. None of this means rewrites are never right. It means they should be the exception, not the default.
Most systems can be rescued
A system that is hard to change is not necessarily a system that needs replacing. It is usually a system that is missing a few things:
automated tests around the parts that matter
a reliable way to deploy
supported versions of the language, framework and dependencies
someone who understands how it actually works
Those are all fixable and they are fixable without stopping the business.
Signs your software can be rescued
In my experience, rescue is the right answer when:
the system still broadly does what the business needs
the problems are about age, risk and confidence, not the core design
the data model still makes sense, even if the code around it is messy
the pain is concentrated in a few areas rather than everywhere
the business cannot afford to pause development for months
That describes most of the legacy systems I see. The software is not fundamentally wrong. It has just been neglected.
When a rewrite is justified
There are times when starting again makes sense. A rewrite is worth considering when:
the underlying design no longer fits how the business works
the data model is fundamentally wrong for what you need to do now
the system is built on a technology that is genuinely dead, not just old
the business itself has changed so much that you would be rebuilding very little of what exists
the cost of every change has become so high that the system is actively holding the business back
The important word in all of those is design. Old code can be modernised. Old versions can be upgraded.
If the fundamental shape of the system no longer matches the business, no amount of refactoring will fix that. Even then, a rewrite rarely needs to be one big switch-over. Replacing a system piece by piece, running the new alongside the old, is almost always safer than a single
cut-over date.
What a rescue actually looks like
At Ainley Digital, a rescue follows the same broad shape every time.
1. Audit
Before anything changes, we need to understand what you have. Which versions are running. Which dependencies are out of date or abandoned. Where the tests are, if there are any. Where the risk sits. That gets written up in plain English, so the business can make decisions based on facts rather than fear.
2. Build a safety net
The reason nobody wants to touch the code is that nobody can tell whether a change has broken something. So the first engineering job is to fix that.
We add automated tests around the journeys that matter most: logging in, taking payments, placing orders, generating the reports people rely on. It does not need to be full test coverage on day one.
It needs to cover the things the business cannot afford to break.
3. Make it deployable
If releasing a change is a manual, nervous process, that needs fixing early. A reliable deployment pipeline means small changes can go live safely and regularly,
rather than being saved up into large, risky releases.
4. Upgrade in steps
With tests and deployment in place, upgrades become far less frightening. We move one major version at a time, fix deprecations as we go and release after each
step.
There is no big switch-over date. The system stays live and usable throughout.
5. Modernise what matters
Once the system is on supported versions and safe to change, we can start improving the parts that are causing the most pain.Refactoring the worst areas.
Fixing performance problems. Removing code that is no longer used.
The goal is not to make the codebase perfect. It is to make it a system your business can rely on and your developers are not afraid
of.
Where agentic development changes the maths
Rescue work has traditionally been slow and expensive. Reading through unfamiliar code, writing tests for software that was never designed to
be tested and working through hundreds of deprecations is time-consuming.
That cost is one of the reasons rewrites have looked attractive. Agentic development changes that. AI agents are very good at the volume work involved in a rescue: mapping how an unfamiliar codebase fits together, drafting tests around existing behaviour and working
through repetitive upgrade changes.
A senior engineer still decides what to test, reviews every change and makes the judgement calls about risk. The mechanical work happens much faster.
That makes rescue cheaper and quicker than it used to be, which tips the balance even further away from a rewrite.
Security is part of the decision
Last week's vulnerability is a good reminder of why this matters. Running unsupported versions of PHP, Laravel or their dependencies means security
fixes stop arriving. When the next vulnerability is announced, a supported system needs a routine
update. An unsupported one may need an emergency upgrade across several major versions, under
pressure, with no tests to tell you whether anything broke.
That is the worst possible time to discover how fragile a system is. Getting onto supported versions before you need to is far cheaper than doing it in a
crisis.
What does a rescue cost?
It depends on the size of the system, how far behind it is and how much of it is
already tested. That is why we always start with an audit, the same way every Laravel upgrade we run begins.
It gives you a clear picture of what you have, what the risks are and what it would take to fix them, before you commit to anything larger.
What I can say is that a rescue is almost always cheaper than a rewrite of the same system. A rescue spends money on reducing risk and improving what you have. A rewrite spends money rebuilding what you already have before it can deliver anything
new.
Ownership does not stop at launch
Software is not finished when it goes live. Languages move on. Frameworks release new versions. Dependencies are abandoned. Security issues are discovered. A system that is left alone does not stay still. It slowly falls behind until it becomes the codebase nobody wants to touch.
The cheapest way to avoid a rescue is regular, steady maintenance: keeping versions current, applying security updates and improving the system a little at a time. That is far less dramatic than a rewrite. It is also far more effective.
So, rewrite or rescue?
My rule would be:
Rescue the system unless its underlying design no longer fits the business. Old code, old versions, missing tests and missing documentation are all problems that
can be fixed in safe, tested steps. A design that no longer matches how the business works is a different problem.
Most systems that people want to rewrite do not have a design problem. They have a confidence problem. And confidence can be rebuilt without starting again. If you have a system nobody wants to touch, the first step is not deciding whether to
rewrite it.
It is finding out what you actually have.