Completely replacing a working system is rarely the best move. Back in 2000, Joel Spolsky used Netscape to show the world what a complete rewrite can cost: almost 3 years passed between the two major versions, and in the meantime the company's market share melted away.1 The reason is still the same today: the new system has to do practically everything the old one did, including the exceptions and business rules that were never written down, because a system's boundary isn't its codebase but its behavior.
When a system changes hands
We often inherit software built by another team. In these cases, the quality of the code is rarely the main challenge. Far more often it's the missing context: why certain decisions were made, what is critical, and where the hidden dependencies are. Every system is a product of its time; the previous team worked within the requirements, deadlines and technological possibilities of the day.
When we were contracted to maintain a sports club's digital platform, we took over a PHP system of roughly 100,000 lines built on Symfony 3. It served the club's news, match and league table data, live coverage and the mobile app's APIs all at once. A system like this can't be switched off for months and rebuilt at leisure: it has to keep running during the takeover too.
Where we started
At the time of the takeover, several problems were present at once. The load on the database server was at a critical level, and on match days it had to handle traffic orders of magnitude higher than usual. The framework version no longer received security updates, and the dependencies included abandoned packages and packages with known vulnerabilities. On top of that, several features and external integrations were broken, including the mobile app's API connections and the social media integrations.
Understand first, change second
Before touching anything, we look for answers to these questions:
- What does the system actually do?
- Where is the real risk?
- What works well and is worth keeping?
- What has to change, and at what pace?
In the sports club project, this led to an intensive stabilization and modernization phase of about three months. Besides the application, we also reviewed and optimized the infrastructure and the network side.
Upgrading the framework isn't a goal in itself, but you can't build long-term on a foundation that no longer gets security fixes. Most of the mechanical rewriting was done by a tool; the real work began where the tool could no longer decide: at the points where the old and new versions behave differently. In the same phase, we replaced the unmaintained packages and restored the connections the platform uses to communicate with the mobile app and social media.
For every component, we had to choose the right strategy: some components were hard to modify, but many parts of the system relied on them. We didn't rewrite these right away; we isolated and stabilized them. Where the existing solution could no longer keep up with requirements and was well contained, redesigning it was the sensible decision.
Every unit of change was verifiable, because in a legacy system even a small change can affect several components. The same logic works at a larger scale: in Martin Fowler's “strangler fig” pattern, new parts are gradually built around the old ones, and the old elements retire one by one.2 Modernization is therefore not one big switchover day, but a process.
We started at two seconds
On dynamic pages, the response time dropped from an average of 2 seconds to a quarter of that, the server load decreased significantly, and removing unnecessary complexity made the code easier to follow. And throughout the process, the platform kept running.
The project showed clearly that most problems aren't worth solving with brute force. In the short term, the faster, cheaper workaround is tempting, but if you can clearly show the client the cost and benefit of the right solution, it pays off better for both sides in the long run.
What does AI change about this?
In this project, the retrospective specification documenting how the system works, and the tests, were still largely written by hand. Understanding the system used to be the most expensive part of taking over a legacy system, and this is where AI has made a real difference. In November 2025, the Thoughtworks Technology Radar placed using GenAI to understand legacy codebases in its “Adopt” ring, meaning it considers it established practice.3 Morgan Stanley's in-house tool turned roughly 9 million lines of legacy code into readable specifications, saving an estimated 280,000 developer hours.4
Today, in a similar takeover, we use AI-based tools to map the codebase, write the documentation, and write tests that capture how the system currently behaves before any changes are made. A developer checks the relationships uncovered, and a human still decides what we keep and what we replace. Since AI also makes rewriting cheaper, making the right decision doesn't lose any of its importance. Quite the opposite: it matters more than ever.
You don't have to start over from scratch
An old codebase or an orphaned system isn't necessarily beyond saving. What works well, we keep; what needs modernizing, we update step by step; and if a part really needs replacing, we plan a safe way to do it. If you also have a system that is an important part of your operations but that few people still understand, let's start by understanding what it actually does. Then let's decide together what stays, what changes, and at what pace.
Footnotes
- Joel Spolsky: Things You Should Never Do, Part I, Joel on Software, April 2000. ↩
- Martin Fowler: Strangler Fig Application, martinfowler.com. ↩
- Thoughtworks Technology Radar, Vol. 33, November 2025 – “Using GenAI to understand legacy codebases”. ↩
- Morgan Stanley DevGen.AI, based on the company's 2025 statements. ↩