Skip to main content
Codentures logo

3 min read

When to modernise rather than rewrite

Rewrite when the existing system genuinely cannot carry the requirement: when the constraint is structural rather than aesthetic. Modernise incrementally in every other case, because a working system encodes years of edge cases that exist in no specification, and a rewrite is a commitment to rediscover all of them under deadline.

  • Modernisation
  • Architecture
  • .NET

Written by the Codentures engineering team.

The request usually arrives already decided. The system is old, changes take too long, the team that built it has gone, and someone has concluded that the responsible thing to do is start again. By the time we are involved the question is not whether to rewrite but how long the rewrite will take.

That is the wrong question about eight times out of ten, and the reason is not sentimentality about old code. It is that a system in production is a specification of its own behaviour, and most of that specification was never written down.

What a rewrite actually discards

Every long-lived system contains a layer of behaviour that exists because of something that happened once. A validation rule added after a bad data import. A retry that exists because a partner's API returns a 200 with an error body. A rounding decision that matches what finance expects rather than what the specification says. None of this is documented, because it was added under pressure by someone solving a real problem quickly.

A rewrite is a commitment to rediscover all of it, in production, under a deadline, with users noticing. That rediscovery is the actual cost of a rewrite, and it is the part that never appears in the estimate because by definition nobody knows what is in it.

The test we apply

Before recommending a rewrite we ask whether the constraint is structural or accumulated. Accumulated problems, such as inconsistent patterns, dead code, missing tests or an outdated framework version, are expensive but tractable in place. Structural problems are not.

A constraint is structural when the requirement cannot be expressed in the existing model at any reasonable cost. A single-tenant data model where the business now needs multi-tenancy with per-tenant isolation. A synchronous request path where the requirement is now sub-second response under a load that the path cannot serve regardless of tuning. A framework that has reached end of security support with no upgrade route. Those are rewrite conditions, or at least conditions for rewriting a bounded part of the system.

Slow, ugly and unfashionable are not rewrite conditions. They are refactoring conditions with an unusually persuasive advocate.

What incremental modernisation looks like

The alternative is not 'leave it alone'. It is a sequence of independently valuable, independently reversible steps, each of which leaves the system running.

  1. Make it measurable. Add logging, tracing and query-level measurement first. Modernising without instrumentation means you cannot demonstrate that any step improved anything, which is how modernisation budgets get cut halfway through.
  2. Make it testable at the seams. Not full coverage, but characterisation tests around the behaviour you are about to move, so that a change in behaviour is visible rather than discovered by a customer.
  3. Extract a boundary. Put an API in front of the part you intend to change, and route existing callers through it. The boundary is now the thing you can replace behind.
  4. Move one workload at a time. Run old and new paths in parallel where the risk warrants it, compare outputs, and switch over when the comparison is boring.
  5. Modernise the deployment path early. A system you can deploy confidently in ten minutes is a system you can modernise. One that takes a scheduled evening and three people is not, no matter how clean the code becomes.
  6. Delete the old path deliberately. Steps that never get cleaned up are how a modernisation becomes a permanent second system to maintain.

The database is usually the real problem

In .NET and SQL Server estates specifically, a surprising proportion of 'the system is too slow to keep' turns out to be a small number of queries, a missing index, a chatty ORM access pattern, or connection-pool exhaustion under concurrency. These are days of work, not quarters, and they are worth eliminating before anyone estimates a rewrite, partly because they may be the whole problem, and partly because if they are not, you have removed the loudest confound from the analysis.

This is why we sell a small fixed-price performance audit as a deliberately low-commitment entry point. It is the cheapest way to find out whether the expensive project is necessary.

When the right answer is to spend the money elsewhere

Sometimes the honest recommendation is that the system is fine and the frustration is organisational: unclear ownership, no test environment, a release process that requires heroics. Rewriting the code will not fix any of those, and it will consume the budget that could have. We would rather say that in a scoping conversation than discover it together in month four.

More insights

Tell us what you need to deliver

A system to build or modernise, or engineers to strengthen your team. The first conversation is with a senior engineer, and you will leave it with a clear recommendation.