Skip to content
All articles
Mobile

Rewrite or refactoring: the question is put wrongly

Andrey Gershengoren · · 9 min

The question usually arrives as an either-or: do we build new or clean up? In that form it is barely answerable, because it is asked about the code while it is decided by something else.

A codebase is not the text of a program. It is materialised knowledge about a domain: thousands of small decisions about why things run this way and not another, made over years. A rewrite does not destroy the bad code. It destroys that knowledge.

Why old code looks the way it does

Legacy code is rarely ugly because beginners wrote it. It is ugly because it survived contact with reality. Every odd branch, every special case, every comment with a year in it is the scar of an incident somebody once had to absorb.

New code has not had that contact yet. It is therefore prettier, and it knows less. A team that builds new walks into the same mistakes again, only this time under deadline pressure and with a running legacy application as the benchmark it does not meet at the start.

That is why asking about code quality misleads. Bad code is a symptom, not a diagnosis, and it says nothing about what replacing it will cost.

The three signals that actually decide

What carries the decision is not properties of the code but three questions about the state of the project.

Testability as a threshold. Not: are there tests. But: can any be introduced without rebuilding first. Where dependencies cannot be substituted, where singletons spread state everywhere, where one class carries five thousand lines, there is no seam to start from. Refactoring there becomes a rewrite in pieces that simply does not call itself one. Naming that honestly beats discovering it over months.

Is the knowledge still alive. Are there people who can explain why the code is the way it is. As long as they exist, a rewrite is waste: you throw away what you could still ask about. For a codebase without a single carrier of that knowledge and without documentation, the calculation inverts, because archaeology becomes more expensive than construction.

Foundation or fit-out. Is the architecture outdated, or only its surface. Objective-C with clean module boundaries can be modernised layer by layer. A two-year-old SwiftUI app with business logic spread across the views can demand the harder intervention more than a ten-year-old UIKit project. The age of the code is the worst of the popular criteria.

The third path the question hides

In practice the answer is usually neither. The new grows inside the old, module by module, with stable boundaries between the worlds. One area is carved out, rebuilt, connected; the rest keeps running and keeps shipping.

The advantage is not technical, it is a risk profile. Every step can be justified on its own and reverted on its own. There is no moment at which everything is new at once and nobody can tell which of a hundred changes caused the fault.

What almost always goes unmentioned is the price, and it is substantial.

The price of the third path

Two paradigms at once, over a long time. The team has to hold both worlds in mind and decide, for every task, which one it happens in. New people have to learn both, not one.

The bridges between the worlds become legacy themselves. Adapters that exist only so two models can talk to each other are code nobody wanted and somebody ends up maintaining.

And the failure mode is not technical. This strategy fails organisationally: after six months the appeal is spent, the migration stands at forty percent, and there it stays. A half-migrated codebase is worse than either clean state, because it carries the drawbacks of both and the benefits of neither. Choosing this path means being able to finish it, and that is a question for the organisation, not the architecture.

Why the rewrite is popular anyway

The failure rate of ground-up rebuilds is well known, and it stops nobody. The reason is not technical.

Reading someone else's code is tedious and dull. Writing new code is easy and enjoyable. The engineering mind reliably rationalises that enjoyment as technical necessity. "That is faster to rewrite" means, in most cases, "I do not feel like getting into it", and the person saying it does not notice the difference themselves.

I do not exempt myself. The sentence has come too easily to me as well, and it sounded like an argument every time.

Where the rewrite wins

Three cases where it is honestly the better choice.

A change of platform paradigm that cannot be done layer by layer, because the new structure cannot accommodate the old one. There the incremental path only produces bridges that all disappear again later.

A codebase that is one or two years old. Little knowledge has accumulated that could be lost, and the scars are not there yet.

And the prototype that ended up in production against every agreement, but is used by so few that migration does not pay. Rebuilding there is not the brave decision, it is the cheap one.

Three questions instead of an assessment

Anyone facing this decision should stop assessing the code and start answering three questions: can I get tests in without rebuilding first. Is there still someone who knows the reasons. Is the architecture outdated, or only its surface.

From those three answers the path follows almost by itself, and in most cases it is neither of the two that were asked about. If the answers are unclear, that is not grounds for a decision but the reason to obtain them first. How to get at them without reading an unfamiliar codebase end to end I have described here: Reading unfamiliar code quickly.

legacymodernizationarchitecturestrangleriOS
Contact

First conversation: 30 minutes, free of charge, no presentation.

You describe the situation, I tell you whether and how I can help. No slides, no sales pitch.