Product engineering · 10 Oct 2026 · 5 min read
Repair, refactor or rebuild: what a product audit decides
A product that was built fast often reaches a point where every change is slow. An audit decides what to keep, what to fix and what to replace.
A product built in a hurry, by a small team or with heavy use of AI tools, often works well enough at launch. Then the pace drops. Each change takes longer. Bugs return. Nobody is sure which parts are safe to touch.
The wrong answer is to rebuild everything. The second wrong answer is to keep patching. An audit sits between the two and makes the choice visible.
What the audit looks at
We look at the product in four layers, in this order:
- Users and outcomes. What people actually do, where they stop, and which parts earn their time. A feature with no users does not need a refactor. It needs a decision.
- Data and integrations. Where the data lives, how it moves, and which outside services the product depends on. Most hard failures start here.
- Code structure. How the parts connect, how a change spreads, and whether tests catch what matters.
- Operations. Deployment, secrets, logging, backups, cost. The things that fail at 2 a.m.
The order matters. Starting with code quality can produce a technically elegant plan for a feature that should be removed. Starting with outcomes establishes what must remain true before the team discusses how to change it.
An audit also needs evidence. Review analytics, support requests, error reports, deployment history and a small set of representative code paths. Interviews alone tend to amplify the most recent frustration. Static-analysis scores alone miss why the system exists. The useful picture comes from both.
Three verdicts
Each part of the product gets one of three verdicts.
Repair. The design is sound, and the problems are local. Fix the bugs, add the missing tests, improve the error messages. Keep shipping.
Refactor. The design is under strain, but the behaviour is right. Change the structure in steps, with tests that protect the behaviour. Users should not notice.
Rebuild. The design cannot support what the product now has to do, or the cost of change is higher than the cost of starting again for that part. Rebuild that part only, behind the same interface, and switch over when it is ready.
Most products end up with a mix. The audit’s value is in saying which part gets which verdict, and why.
Estimate the cost of change
The decision is not based on code appearance. It is based on how safely and quickly the product can change. Pick two or three realistic requests from the roadmap and trace what each would require. If a small interface change crosses the database, API and deployment configuration, the architecture is charging interest on every release.
Measure recurring work as well. Manual deployments, repeated data repairs and support issues are part of the cost even when they do not appear in engineering estimates. A local repair that removes a weekly operational task may be more valuable than a broad refactor that makes the code look cleaner.
The team should also estimate migration risk. Rebuilding a data layer involves more than writing a new schema. Existing records need mapping, old links may need redirects and rollback must remain possible. A rewrite estimate that excludes migration is not an estimate of the real work.
Signs that point to each verdict
- Bugs return in the same place after each fix: refactor.
- One change touches many files for no clear reason: refactor.
- Core data has no clear owner or schema: rebuild that layer.
- Nobody can deploy without the original author: repair operations first.
- A feature is complex and nobody uses it: remove it. That is a fourth verdict, and often the best one.
Other signals need context. An old framework is not automatically a reason to rebuild. If it remains secure, supported and easy to change, upgrading may create cost without user value. A modern stack is not automatically healthy either. New code can still have unclear ownership, duplicated logic and no recovery path.
AI-generated code deserves the same test as any other code. The question is not who or what produced it. The question is whether the behaviour is understood, important paths are tested, dependencies are appropriate and another person can change it safely.
Protect behaviour before changing structure
Refactoring is safest when the team can describe the behaviour that must not move. Add tests around important journeys, data transformations and external contracts before reorganising the implementation. These are characterisation tests: they record what the product does now, including awkward behaviour that may need a separate product decision.
Then change one boundary at a time. Introduce a clearer interface, move one responsibility behind it and deploy. Small reversible steps expose incorrect assumptions early. A long-lived rewrite branch hides them until the new system has to meet the old world all at once.
For a partial rebuild, run old and new paths together where practical. Compare outputs, migrate a small group and keep a rollback route. The goal is not zero risk; it is making the risk observable and contained.
The output
An audit should end in a short document: what we found, the verdict for each part, the order of work, and what the first two weeks look like. It should fit on a few pages. If it does not, it is a list of complaints, not a plan.
Each recommendation needs an owner, a reason and a stopping condition. “Improve test coverage” is not a plan. “Protect the three payment failure paths before changing the billing adapter” is. The first item on the roadmap should prove that the proposed direction makes future work easier.
Include what will not be changed. A clear non-goal prevents the audit from becoming permission to modernise everything. It also gives stakeholders confidence that the team understands which parts already work.
The goal is a product that can change again, at a pace the team can keep.
Keep reading



