There are multiple approaches to modernizing legacy software. Typically, they are framed as the seven Rs: rehost, relocate, replatform, rewrite, repurchase, retire and retain. While they are all legitimate in the right modernization context, there are nevertheless trade-offs in choosing one over the others.
What this means in practice is that organizations will often select an approach (or combination of approaches) that seems less risky or easier at the cost of future value and long-term business impact. So, while, say, rewriting a legacy system may be the right approach in terms of delivering value, it's often avoided because it's deemed too difficult.
However, it doesn't have to be this way: an approach we call 'test-first modernization' can upend common trade-offs and make it possible to rewrite a system without increasing difficulty or risk.
The traditional trade-off
As you move from conservative tactics like replatforming towards more impactful strategies like rewriting, it becomes harder and harder to verify the similarity in functionality or indeed, whether you’ll be successful. In many scenarios, when it’s not clear if functionalities match or to what extent they do, it can be hard for businesses to take a decision to switch off the mainframe workload or to migrate users off it.
It’s not just about whether the new system works in isolation: it’s about ensuring everything still works together. That can mean extensive integration and regression testing and painstaking efforts to reconstruct the full system around the components that have been modernized. This verification effort is time-consuming and hard to properly scope. In turn, this can make modernization feel riskier, even when the end goal of a rewrite — modern, understandable code and a shedding of technical debt — is clearly better.
That’s why many enterprises are forced into a trade-off between easily verifying similar functionality in code that’s difficult to understand, or the more challenging process of verifying similar functionality in modern, idiomatic code, which will have much greater long-term benefits.
Replatform |
Refactor |
Rewrite |
|
Verification of similarity in functionality |
Easier |
Easier |
Harder |
Human-readable, idiomatic, modernized code |
No |
No |
Yes |
Ability to unlock future value |
Least |
Low |
High |
But what if you could break that verification trade-off and harness AI in a way that ensures the work is done properly?
Introducing test-first modernization
We call this approach test-first modernization. It’s a way to apply AI and automation while keeping humans in control. It’s built around a deceptively simple idea:
Use the behavior of the running system as the source of truth.
You select a single component and capture how it behaves in production as shown through the inputs it receives and the outputs it produces. This behavior inherently includes the interactions with other parts of the system and databases, and it effectively becomes the test harness that defines the expected behavior of new code in concrete terms.
Then this is where AI thrives: you can apply the best-performing AI tooling to generate idiomatic, modern code against the specification. It doesn’t have to be right the first time, because if the output fails the safety harness, the failures are just fed back into the process. In other words, you iterate, refine and repeat until it passes the test — which will sometimes take just a few minutes. For larger and more complex workloads, humans can interject at any point to provide the system more feedback to help it converge to the right behavior.
Once you have behavioral parity, you can run it in parallel with the rest of the system. At this point, you can test it for performance equivalency within the running system. Only then do you retire the legacy component with the confidence that nothing has been lost or broken in the transition.
This ability to embed functional verification directly into the code generation process itself decreases the difficulty and risk exposure of what has traditionally been the most difficult yet most rewarding of the R’s.
Why it matters
This test-first approach offers three big advantages:
You get idiomatic, modern code and preserve behavior. Traditional transpilers can create code quickly, but teams end up spending significant time untangling and rewriting that output just to make it usable. Manual rewrites might create better code, but the time and effort it takes is often prohibitive. With test-driven modernization, the code generated just as quickly, but it comes out idiomatic and functionally equivalent.
You save immense amounts of time without sacrificing quality. Humans are never out of the loop, but they don’t have to do the brute-force labor of hand-writing new code. Instead, AI handles the heavy lifting of code generation, using the tests to converge on the exact output expected. Engineers can step in to review, shape and steer the outcomes.
You clean up technical debt as you go. Because the functional tests are generated from actual observed behavior, they only include what’s actually used. That means unused code paths from decades ago, like the code managing the smoking section of airplanes, don’t get replicated. What remains is not just a modernized system, but one that’s easier and faster to understand, maintain and evolve.
Modernization is hard, and every project is different. But test-first modernization breaks the traditional tradeoff between difficulty and quality — and allows you to apply even the most aggressive, AI-powered strategies in a way that makes rewrites safer and faster than ever before.
Disclaimer: The statements and opinions expressed in this article are those of the author(s) and do not necessarily reflect the positions of Thoughtworks.