Refactor or rewrite: deciding on evidence, not mood
Which signs point to a rewrite, which to refactoring, how to gather them in a week, and why the hybrid option usually wins.
Sooner or later, in every project that grew out of a prototype, someone says “it’d be easier to rewrite it”. Sometimes that’s true. More often it’s fatigue spoken in a confident voice. New code always looks cleaner than old code, if only because it doesn’t exist yet.
The decision should rest on a handful of observable things rather than on how the code feels. You can gather them in a week, and that week costs less than getting it wrong in either direction.
What actually hurts
Start with what gets in the way of the business, not with what’s ugly. Ask:
- How long does a typical small change take from ticket to production?
- How often does a change in one place break something somewhere else?
- Are there parts of the code nobody wants to touch?
- Does anyone on the team understand how the core flow works end to end?
If changes ship quickly and rarely break things, the code can be as ugly as it likes. There’s no reason to rewrite it. If every task turns into an investigation, it’s no longer about aesthetics.
Signs a rewrite is worth it
- The data model is wrong. This is the big one. If entities are tangled, relationships are stored in JSON strings and one concept lives in three tables under three names, refactoring the code on top of that schema won’t help. Code can be fixed piece by piece. A schema is much harder.
- The platform can’t do what you need. An app generator you can’t export from, a framework missing a capability you depend on, hosting that won’t run background jobs.
- Security can’t be fixed in spots. When access is checked in a hundred places, each differently, with no shared layer, building that layer fresh is easier than finding all hundred.
Signs refactoring is enough
- The data model is broadly right, even if fields have odd names.
- The problems are concentrated in a few modules rather than smeared across everything.
- The main flows work and users are happy with them.
- The team can describe what each major part does.
Why rewrites are dangerous
Old code holds knowledge that isn’t written down anywhere: odd edge cases, fixes made after complaints, arrangements with partners. A rewrite loses it, and you rediscover it the hard way, on your users.
The second risk is the freeze. While the rewrite is in progress, no new features ship, yet the old version still needs maintaining. Two systems instead of one, both unfinished. A rewrite that runs longer than a couple of months without intermediate releases often never finishes.
The hybrid usually wins
There’s almost always a third path: replace things piece by piece while the system keeps running.
- Start with the data. If the schema is bad, design the right one and move one complete flow onto it, data migration included. Everything else reads through a compatibility layer for now.
- New modules on the new foundation. New features are built the new way, and old code moves over when work reaches it.
- Worst first. You don’t rewrite “everything”, just the two or three modules with the most bugs and changes. The rest gets refactored along the way.
- Before replacing, test current behavior. Not how it should work, but how it works now. Then you see what you changed and can decide whether you meant to.
Each step ships to production on its own. If money or priorities run out halfway, you’re left with a working system that got better, not two halves of different systems.
How to write the decision down
After a week you should have a short map: a list of modules, and for each one its condition, how often it changes and breaks, and a verdict of keep, refactor or replace. That’s something you can discuss with the team and with whoever pays for the work, unlike “I just feel it’s all bad”.
SUBXCAT does this kind of assessment for projects that grew out of prototypes, and helps carry out the chosen path without stopping the product.