Software rewrites are easy to approve in a meeting. The old system looks dated, the new stack is familiar, and a clean rebuild promises to remove years of awkward decisions.
Then someone discovers that the “simple” pricing rule has 14 exceptions, three of them exist only for one major customer, and the original developer left four years ago.
Old software can be frustrating. It can also contain a lot of hard-won knowledge. Before replacing it, the useful question isn’t whether the code looks modern. It’s whether the working parts can be reached safely from the system you want to build next.
Start with the job, not the language
Teams often begin a modernisation project by comparing frameworks. That’s premature. First write down what the old component does, who calls it, what data it expects and what happens when it fails. Include scheduled jobs, file shares, database tables, scripts and manual workarounds, not just what appears in an architecture diagram. TechGuide’s explanation of how APIs connect software is a useful reminder that the interface matters as much as the code behind it. Many rewrite surprises begin at a boundary nobody documented.
Take a Ruby service that needs to process barcodes in PDFs. Building a Ruby barcode scanner SDK around selected C++ functions can preserve the mature document-processing engine without forcing the team to recreate it in Ruby. The wrapper still needs tests, packaging and version control, but the proven engine continues doing the difficult work underneath.
This distinction changes the business case. Replacing a 12-year-old user interface may be straightforward because its behaviour is visible. Replacing the calculation engine behind it is different. That engine may encode rounding rules, regional formats and customer-specific limits that nobody has written down. A cleaner implementation can still produce the wrong invoice.
Before estimating a rewrite, run the old system against representative inputs and save the outputs. Include ordinary cases, malformed files, unusually large records and examples that have caused support tickets. Those results become a behavioural contract. Without one, the new team is judging correctness by whether the fresh code appears sensible.
That’s a weak standard when money, records or customer workflows are involved. Two systems can look identical during a demonstration and behave very differently when a file contains an unexpected character, a payment is retired, or a user enters a date in the wrong format.
The less visible the old component is, the more carefully it should be tested before anyone promises to replace it.
A wrapper can buy time without creating a dead end
A wrapper is a translation layer. It lets code written in one language call functions from a library written in another, while converting values, errors and objects into forms the newer application understands. The official SWIG documentation describes how C and C++ declarations can be exposed to languages including Ruby, Python, Java and C#. The approach is well established, but implementation quality still matters.
The best wrappers are deliberately smaller than the libraries beneath them. If a native library exposes 600 methods and the new application needs 18, wrapping all 600 creates a much larger testing and maintenance surface. Expose the operations the product genuinely uses, give them names that make sense in the new language, and hide details such as pointers, allocation and platform-specific file handling.
This is one place where teams regularly overbuild. They generate access to every available method because it feels more complete, then spend months maintaining functions nobody has called. A smaller interface is easier to understand, easier to test and less likely to break when the underlying library changes.
Error behaviour deserves particular attention. A C++ function might return an integer code, write details to a log and leave cleanup to the caller. A Ruby application will usually expect an exception with useful context. Making the function callable isn’t enough. The wrapper has to translate failure in a way the surrounding application can handle consistently.
Native libraries also have practical baggage. They must be compiled for the operating system and processor where they’ll run, and an SDK update may require regenerated bindings. Memory leaks can cross the language boundary, while a native crash can bring down the host process.
That doesn’t make wrappers a poor choice. It means they should be treated as production code, not scaffolding generated on a Friday afternoon. Pin the library and wrapper versions together, build them in a repeatable environment, and test the packaged artefact that will actually be deployed.
A wrapper should also have an owner. Someone needs to know which versions are supported, how releases are built and what happens when the underlying library changes. Without that ownership, a temporary bridge can quietly become another piece of legacy software.
Modernise at the seams
A rewrite becomes safer when users don’t have to move from the old system to the new one in a single release. Microsoft’s Strangler Fig pattern puts a façade or proxy in front of an existing application, then gradually directs selected requests to replacement services. The old and new systems operate side by side until enough behaviour has moved.
Consider an order platform that handles customer accounts, inventory, pricing, payments and shipping. Replacing all five areas at once creates a project whose success depends on every team finishing together. A narrower first move might keep pricing and inventory in the old application while sending shipping quotes to a new service. If that service fails, traffic can return to the previous path.
The seam could be an API, a message queue, a command-line interface or a file exchange. TechGuide’s overview of modern integration architecture shows why the choice affects latency, coupling and operational effort. What matters is having an explicit contract and a place where routing can change without editing every consumer.
Good execution is fairly unglamorous. The team logs which route handled each request, compares old and new outputs in shadow mode, and defines a rollback condition before switching live traffic. A one-cent difference may be harmless in an estimate and unacceptable in a payment ledger. The process owner should decide that tolerance.
Shadow mode is especially useful when mistakes would be expensive. The new service receives the same requests as the old one, but its answers aren’t shown to customers yet. Engineers can compare results, investigate differences and see how the replacement behaves under real load without letting it control the transaction.
Incremental replacement also tests whether the proposed architecture is genuinely better. A service that looked elegant on a diagram may be too slow once it makes four network calls for work the old process completed in memory. Finding that out after moving one workflow is inconvenient. Finding it out after a two-year rebuild is expensive.
The same principle applies to internal tools. A company may not need to rebuild an entire desktop application just to replace its reporting screen. Exposing the existing calculation engine through a controlled interface and building a new front end can remove the part users dislike without disturbing the part that still works.
Know when the old core has to go
Wrappers and adapters work best when the old component is stable, understood and still supportable. They’re less attractive when the dependency can’t run on a maintained operating system, relies on an abandoned compiler, has unresolved security problems or comes with licensing terms that block the planned deployment.
Performance can settle the question too. Crossing a language boundary once to process a document is different from crossing it millions of times inside a tight loop. The wrapper may add conversions, copying and garbage-collection pressure. Measure the real workflow with realistic data rather than timing a tiny sample and multiplying the result.
Supportability matters just as much as raw speed. A component may still run perfectly, but if nobody can rebuild it, test it or diagnose a failure, the business is carrying more risk than the uptime figures suggest. That risk often stays hidden until a server fails or an operating-system update removes something the application depended on.
The same caution applies to AI-assisted conversion. Coding agents can translate syntax, create tests and handle repetitive integration work, as TechGuide noted in its look at the shift from code assistants to AI teammates. They can’t reliably recover a business rule that exists only because a sales manager complained in 2018. Faster code generation doesn’t remove the need to identify what the software must preserve.
A full rewrite is easier to justify when the old data model blocks new features, releases depend on someone manually copying files, or routine changes touch several fragile subsystems. Even then, keep the evidence. Old outputs, logs, support cases, and customer examples may be the best available specification.
The strongest decision is sometimes mixed. Keep a proven image-processing library behind a wrapper, replace the ageing desktop interface, and move account management to a service with a cleaner API. Architecture doesn’t need to be ideologically pure. It needs to be understandable, testable and affordable to operate.
Wrap-up takeaway
Old code shouldn’t survive merely because replacing it feels risky, but age alone isn’t a reason to throw it away. Start by separating valuable behaviour from the obsolete delivery mechanism around it. A focused wrapper can preserve a difficult, proven capability while the rest of the product moves to a stack the current team can support. An adapter or façade can make the migration gradual enough to measure, reverse and improve. Rewrite the core when support, security, performance or business constraints make continued reuse the worst option. Today, choose one troublesome legacy component and document its inputs, outputs, dependencies and five most important edge cases before anyone estimates its replacement.

