Rescue or rebuild: deciding what to do with a legacy system
When a codebase is worth stabilising and when it is not, what an assessment should tell you before you choose, the risks in each direction, and how to phase a migration so it can pause.
The system still runs the business. Invoices go out, customers log in, money comes in. It is also the system nobody wants to touch: every change breaks something unrelated, the person who understood it has left, and the hosting bill quietly includes a server nobody can explain. Somebody has said the word “rewrite”, and somebody else has said it will take twice as long as anyone thinks.
Both of them may be right. This post is the framework we use to decide: when a codebase is worth stabilising, when it is not, what an assessment should tell you before you choose, what goes wrong in each direction, and how to phase a migration so that it can stop halfway and still leave you better off.
Start with what the system knows
A system that has run a business for years holds knowledge that exists nowhere else. Not in the documentation, if there is any, and often not in anybody’s head. It is in the code: the rule that a customer on a particular plan is billed on the first working day rather than the first, the discount that applies only when two conditions are true, the report that excludes a category because an accountant asked for that four years ago.
That knowledge is the strongest argument against a rebuild, and the most underestimated. A rewrite starts from what people remember the system does, and people remember the main paths. The edge cases come back one at a time, as complaints, after launch.
We hit a small version of this in our own product. In rentiQ, an invoice can carry arrears brought forward from earlier months, so adding up invoice balances counts the same debt twice. The correct figure is what was charged minus what was paid, and that rule now sits in the code and in the reports built on it. A rebuild that missed it would produce arrears reports that were confidently wrong, and nothing about a single invoice would look out of place. Every legacy system has rules like that. The question is whether you can find them before you replace the code that holds them.
What an assessment should tell you
Before anyone chooses, somebody should read the system with these questions in mind. Each one has an answer you can check rather than an opinion you can argue about.
Can it be built and deployed from scratch, on a clean machine, from what is in the repository? If not, nothing else can be measured until it can, and this alone is often the first week of a rescue.
Do the tests protect anything? A test suite’s existence proves little. We have found tests in our own code that passed against behaviour they were never reaching: a guard that only ran in one environment, tested in another where it never ran; a copy button whose test checked that it was visible while it copied nothing at all. The useful question is whether breaking the important behaviour makes a test fail. Try it, on purpose, on the three things that matter most.
Does it do what its configuration says it does? Systems can look healthy while a whole layer does nothing. On our own site, a move between hosting platforms turned a whole set of caching rules into rules the new platform does not read. Nothing failed. Had it shipped, every page would have rendered correctly while being rebuilt from scratch on every request, and only a check of the behaviour, not of the configuration, would have shown it. Configuration is a claim. The assessment checks the behaviour.
How far behind are the framework and its dependencies? A version still inside its support window is routine maintenance. One that has fallen out of it stops receiving security fixes, and every year it stays there makes the eventual upgrade bigger.
Where does correctness live? Money, stock, entitlements, anything a customer or an auditor will check. Find the code that decides those numbers and judge it separately from the rest, because it is the part where a mistake is expensive.
Is the data sound? Orphaned records, duplicated customers, balances that do not match their transactions. Bad data survives any rewrite, and a migration is the moment it gets noticed, usually by a customer.
Who can change it today, and how long does a small change take? The cost of the system is not its hosting bill. It is the cost of every change the business needs and cannot get.
What has broken in the last year, and why? Incidents show where the real weaknesses are faster than any code review.
The assessment’s output should be short: the risks ranked, what each would cost to address, and a recommendation with the reasoning written down. If it cannot fit on a few pages, it has not been finished.
When to stabilise it
Rescue is usually right when the core of the system is sound and its problems are around it: missing tests, no deployment process, outdated dependencies, no monitoring, nobody who knows it. Those are fixable without touching what the business relies on, and each fix makes the next one safer.
Signs that point that way:
- The data model fits the business. The tables describe the real things, even if the code around them is untidy.
- The behaviour that matters is correct, even if it is hard to change.
- The framework is old but still has a supported upgrade path.
- Most of the pain is in how changes are made, not in what the system is.
A rescue usually begins with the unglamorous work: make it build and deploy repeatably, add monitoring so failures are seen, write tests around the behaviour that matters before changing it, upgrade dependencies in small steps. Only then does it change anything a user would notice.
When to replace it
Rebuild, or more often replace in parts, when the problem is the core rather than the surroundings:
- The data model contradicts the business, so every new feature is a workaround.
- The platform has no supported path forward and no security fixes.
- Correctness problems are structural: the same number computed in five places, five different ways.
- The system cannot meet a requirement that is no longer optional, such as a regulatory change it was never designed for.
Even then, “rebuild” rarely means switching everything off one weekend and switching the new system on the next. That approach has the highest risk and the latest feedback.
The risks in each direction
Rescue risks. Sunk cost: continuing to invest in something that cannot reach where the business is going. A ceiling: some limits are in the architecture and no amount of tidying lifts them. And drift: a rescue without a clear end point becomes permanent maintenance with a more optimistic name.
Rebuild risks. The forgotten rules described above. A long period in which the business gets nothing new, because everything goes into reaching parity. Two systems running at once, each needing support. A data migration that has to be right the first time it meets real data. And the second-system temptation: adding everything the old system lacked, which turns a replacement into a larger project than the original.
Neither list is a reason not to proceed. Both are lists of things to plan for.
Phase it so it can pause
Whichever direction you choose, structure the work so that stopping after any phase leaves the business better off than before it started. Budgets change and priorities move, and a migration that only pays off when it is fully finished is a bet that neither will.
Phase one is the same either way: make the system safe to change. Repeatable builds, deployment, monitoring, backups that have been restored, and tests around the behaviour that matters. This is worth doing even if nothing else ever happens, and it is the foundation for both a rescue and a replacement.
Then move one part at a time, behind the old system’s front door. Put a routing layer in front of the old system, move one feature or one workflow to new code, and route only that traffic to it. The old system keeps doing everything else. If the new part misbehaves, routing sends traffic back. Each step is small enough to reverse.
Choose the order by risk and value, not by convenience. Early moves should be parts that are valuable to improve and low risk to get wrong, so the approach is proven before it touches the parts where mistakes are expensive. Money handling moves late, after the team has done the routine moves and the reconciliation checks are in place.
Migrate data with a way back. Move it in a form you can verify, run both systems against it for a period where that is possible, and compare the outputs. A migration you cannot reverse is a migration you have to get right on the first attempt.
Stop when the remaining parts are not worth moving. Some parts of an old system are stable, rarely changed and doing their job. Leaving them where they are, behind a clean interface, is a legitimate ending.
What to do this month
If you are weighing this decision now, three things are worth doing before anyone writes code. Get the system building and deploying from its repository, because every later step depends on it. Write down the five numbers the business relies on and find the code that produces each one. And ask for an assessment that ends in a recommendation and its reasons, not a list of everything that is wrong.
That assessment, and the stabilise-first work that follows it, is what our modernization and rescue work covers.
Next step
Want this applied to your situation?
A post has to generalise. A 30 minute call does not. Tell us where you are and we will tell you what we would do first.
The first call is free and takes 30 minutes. We reply within one business day.