home / blog / Rebuild or refactor

Rebuild or refactor: how to choose for your application?

Improve the existing code or replace it? Definitions, the criteria that tip the balance, a decision table and a middle path: the progressive rebuild.

Adrien Le Breton 6 min read

In short: refactoring improves the structure of the code without changing what the application does; a rebuild replaces all or part of the system. In production, you almost always start by adding tests and refactoring, then replace module by module. A full rewrite is justified when the technology or the data model no longer holds up.

Rebuild, refactoring: what are we talking about?

Refactoring changes the internal structure of code without changing its behaviour. Martin Fowler, who popularised the term, defines it as "a disciplined technique for restructuring an existing body of code, altering its internal structure without changing its external behavior" (refactoring.com). Renaming, splitting an overlong function, removing duplication, isolating a dependency: users see no difference, and the team saves time on every change that follows.

A rebuild replaces all or part of the system: new architecture, new framework, sometimes a new data model. It can happen in one go, the full rewrite (the "big bang"), or piece by piece, the progressive rebuild.

Both questions usually come up with a so-called legacy application. Michael Feathers gives a useful definition in Working Effectively with Legacy Code: code without tests (an analysis of that definition). Age matters little. Without tests, every change is a gamble, whether you refactor or rewrite.

What is technical debt, and how do you spot it?

Technical debt is whatever in the code makes changes more expensive than they should be: shortcuts taken to ship fast, ageing dependencies, a structure that no longer matches the business. The metaphor comes from Ward Cunningham, and Martin Fowler sums up how it works: the extra effort it takes to add a feature is the interest paid on the debt (TechnicalDebt). Some debt is normal, and sometimes worth it when an idea has to be tested quickly. The trouble starts when the interest eats the time meant for new work.

A few clues are often enough to spot it. A small change takes noticeably longer than it did two years ago. A good share of bugs are regressions. Some files are touched in every release, yet nobody dares to rework them properly. Those areas, usually poorly tested, are the first candidates for refactoring.

Which criteria should guide your choice?

Is the technical debt localised?

If bugs and slowdowns are concentrated in two or three modules, refactoring those modules costs less than replacing everything. If every part of the code depends on every other part, each fix triggers another, and refactoring moves too slowly to keep up with incoming requests.

What is the test coverage?

Automated tests on the critical flows make refactoring safe: you change the structure, the tests confirm the behaviour hasn't moved. Without tests, the first step is the same whatever you choose: write tests that pin down the current behaviour. A rewrite started without that safety net loses business rules along the way that nobody had documented.

Is the technology still maintained?

A maintained framework can be upgraded step by step. An abandoned one changes things: AngularJS, for example, has not been supported since January 2022 (official announcement). Its security flaws are no longer fixed, and every month that passes makes the exit more expensive. That situation points towards a rebuild, preferably a progressive one.

Who will be able to maintain the system?

If nobody, in the team or on the market, knows the stack any more, refactoring means investing in a dead end. Conversely, switching technology to follow a trend costs a lot for a thin gain. Migrating a healthy Angular application to React "because everyone does it", for instance, forces a certain rewrite for a gain that stays theoretical.

What is the business risk?

An application that handles invoicing or customer relations can't stop. A full rewrite often means freezing changes for months, or running two systems side by side. Joel Spolsky called Netscape's decision to rewrite its browser from scratch the "single worst strategic mistake" a software company can make (Things You Should Never Do, Part I, 2000). The higher the business risk, the stronger the case for a progressive approach.

Rebuild or refactor: what does the decision table say?

Criterion Lean towards refactoring Lean towards a rebuild
Technical debt Concentrated in a few modules Everywhere: each fix breaks something else
Tests Critical flows covered, or easy to cover Impossible to add without reorganising the code
Technology Maintained, upgrades possible Abandoned, no more security fixes
Skills The team knows the stack, hiring is possible Nobody masters it any more
Data model Fits today's business Worked around everywhere, no longer reflects the business
Business risk Can't stop, continuous changes Isolable scope, or a business changing in depth
Functional need The product does the job, just slowly The product must do something else: mobile, offline, multi-tenant

If most rows lean right, a rebuild is justified. It still doesn't have to be done in one go.

How do you replace an application without stopping it?

A progressive rebuild replaces an old application piece by piece while it stays in production throughout the transition. Martin Fowler named it the "strangler fig", after a fig that grows around a host tree until it takes its place (StranglerFigApplication). In practice, you put a routing layer (a proxy or an API gateway) in front of the old system. Each new or rewritten function is served by the new code; everything else keeps going through the old one. Module after module, traffic switches over, then the now-useless old code is deleted. For Fowler, the benefit is that investment and returns arrive gradually and visibly, instead of as a single bet at the end. The price: transitional code so that both systems can coexist, and close attention to the data they share.

To get started, pick a useful but low-risk module with clear boundaries. Put it under tests, build its replacement, switch the traffic, watch, then delete the old one. This first module is a dry run: it lets you fine-tune the deployment pipeline and data synchronisation before you tackle the sensitive parts.

Which warning signs should you watch for?

On the application side, these signs call for action:

  • A simple change takes weeks.
  • Fixing one bug creates another somewhere else.
  • A module has become untouchable, or only one person knows how it works.
  • Dependencies are no longer maintained or have known vulnerabilities.
  • Every release is manual and dreaded.
  • You struggle to hire for the stack.

During a rebuild, other signs show it's going off track: the switchover date slips at every steering meeting, the old system still gets changes that will have to be ported to the new one, and after several months no user has touched the new code. If that happens, go back to smaller deliveries.

Why run an audit before deciding?

The choice between a rebuild and refactoring rests on facts nobody has in front of them at the start: where the debt is concentrated, how much of the code is tested, which versions are still running, which business rules are written down nowhere. A short audit, usually 3 to 5 days, establishes them. The auditor reads the code, talks with the team that maintains it, checks the state of the dependencies and spots what slows changes down. The output is a debt map and a costed action plan that compares the options on the same basis: refactor this area, replace that module, or rewrite. Without that assessment, the decision rests on impressions, and impressions often push towards redoing everything. Set against a project of several months, those few days weigh little.

That's the idea behind our pre-decision audit. For budgets, we give orders of magnitude in the article on the cost of a legacy application rebuild. And if your existing system is a recent prototype, the logic is the same, with its own pitfalls: see how to take over an AI-generated MVP.

And at Neodev?

Legacy application rebuilds are one of the things we do. We start with a short audit, then move in reversible increments covered by regression tests, with a system that stays in production at every step. Your team gets the documentation and the knowledge along the way.

Tell us about your application: we reply within 24h, and the first call is free.

Adrien Le Breton founded Neodev and leads the collective. He has been doing software consulting and development since 2018, including more than two years in technical leadership. More about the author.

Is your application holding you back?

A short audit tells you whether to refactor, replace step by step or rewrite, with a costed plan.

Book a call