Teams begin changing a legacy system before a developer has documented what that system must continue doing, and that is the mistake the rest of the project inherits. The first module looks like a tidy-up, turns out to be entangled with three others no one mentioned, the tests get deferred until the structure settles, and the work drifts into an unplanned rewrite with no cutover plan and no lead who can state what the business is still owed by the software.
Refactoring keeps its meaning only through preservation. The code is rearranged; the behaviour the organisation depends on does not move. LANSA's guide to working effectively with legacy code makes the same distinction, describing refactoring as restructuring a system so that it becomes easier to maintain and safer to change while the behavioural contract stays as it was, and noting that these systems sit inside live production workflows where a small change can still interrupt something important. Once behaviour moves, the work needs a rewrite's planning, budget and cutover discipline.
This page sets out the checks that decide whether legacy software refactoring is what you are doing, or something else carrying the same label. The checks are grouped by the moment each one runs, and every check asks about something you can confirm from records and from the staff who run the system.
Checks that run before the work is authorised
The principle: a programme that cannot state what must not change has not earned the right to change anything.
- One designated manager has the decision and can halt the work without convening a committee.
- One written statement specifies the behaviour the refactor must preserve, and it carries a business lead's authorisation alongside any engineering authorisation.
- The first increment touches modules that the change history shows as frequently modified, defect-heavy, or blocking a committed roadmap feature.
- The dependency map records what calls each module and what each module calls, including batch jobs, file drops, message queues, scheduled tasks and screens that other systems read.
- The support status for every runtime, library, database and operating system in scope is settled in writing, with an end-of-support date where one exists. BCN's account of refactoring legacy applications views unsupported components as a continuity risk, because a patch designed for a supported system can break an unsupported one.
- The system saves a route decision that contains the routes it rejected and why it rejected each.
- A list of modules outside the scope specifies which parts of the software will not be changed, with the reason for each.
- The behaviour that is deliberately being changed, if any, is listed separately from the behaviour being preserved. The scope is not yet understood if the two collections are merged.
- The funding covers the work of preservation as well as the work of change: the harness, the baseline, the dual running and the review time.
- A developer can answer, in a single sentence, what the system would have to do for this refactor to be judged a defeat.
Checks that run before the first change is sent from your machine
The principle: preservation is a property of evidence. Intent, however sincere, proves nothing.
- A behavioural baseline was captured from the running system itself. The documentation may be years out of date and serves as a cross-check.
- The baseline covers the paths the business actually uses, including the awkward ones: month-end runs, exception handling, manual corrections and the reports that no one admits to needing.
- Characterisation tests exist for the modules in the first increment, written to record what the code does now, whatever the design intended.
- The test harness runs against the complete increment, with the edited function covered inside it.
- A rollback path exists and has been exercised at least once outside production.
- Refactoring changes and functional changes stay in separate changesets. LANSA's guidance on this is blunt: mixing structural improvements with feature delivery hides bugs and blurs traceability.
- The safety net contains all four parts before the first commit: an automated test suite, monitoring and alerts, a rollback mechanism, and a staging environment that mirrors production. Kodesage's guide to refactoring legacy code shows that those four are the components that make the work possible, and observes that legacy systems rarely arrive with the test coverage the work needs.
- The build produces a deployable artefact from a clean checkout, so the release does not depend on one engineer's machine.
- A feature flag or equivalent switch can turn the restructured module off without a deployment.
- The team can name the observable signal that would prove the refactor had broken behaviour. If no one can identify it, the increment is not ready to start.
Checks that run before a tool or a supplier is given write access
The principle: the risk stays with you when another agent or team uses the keyboard.
- The tool or supplier has read the preservation statement and can restate it in their own words.
- The unit of work handed over is a bounded refactor with a stated behaviour contract, and acceptance runs against the baseline suite.
- Every proposed change arrives as a reviewable set with the tests that cover it.
- Their tooling is scored on your baseline, and any claim about speed is filed separately from their evidence about correctness.
- Where a language-model agent is doing the editing, the limits are written down. RefactorBench, the Microsoft and UC Berkeley benchmark built from handcrafted multi-file refactoring tasks, found that agents solved a minority of those tasks while a human developer working under a short time constraint solved most of them, with the failures clustering around keeping track of what had already been changed.
- The manager who approves each increment is specified, and that manager is part of your organisation.
Checks that run inside every increment
The principle: the increment carries the risk, so it has to be small enough to be reversed in one move.
- The increment can be reviewed in one sitting and reversed by reverting one change set.
- Each increment keeps the system in a state that can go to production on its own.
- No increment depends on a later increment having already shipped.
- Work is sequenced by impact, measured through change history, defect data and the roadmap items the business has already committed to. Miquido's analysis of legacy app modernisation puts it sharply: refactoring without prioritisation is rewriting at low velocity.
- The test suite still passes without a developer manually re-testing unrelated features.
- Performance is measured before and after each increment, and the numbers go into the release evidence.
- The refactor has not become a redesign of the data model or the domain boundaries. If it has, stop and re-route before another increment is planned.
- The business client reviews each increment against the preservation statement to see if it was true.
- The team has left the rarely changed, low-risk modules alone. Those modules can stay as they are for years at no cost.
Checks that run at each release limit
The principle: a release is where preservation becomes a claim you have to defend.
- The release documentation separates behaviour-preserving changes from behaviour-changing ones, so a reader can tell which is which without asking.
- Where dual running is feasible, outputs from old and new paths have been reconciled and the differences explained.
- A rollback has a designated lead and a time limit, both agreed and documented before the release window opens.
- Operations and support staff have a written note of what changed and of the behaviour that stayed the same.
- The incident rate in the period immediately after the release is compared with the same module's own baseline.
- The route decision for the next system in the estate is re-read against the alternatives that were rejected, and it remains valid.
Checks that run a quarter after the increment has shipped
The principle: the only honest test of a refactor is what the system costs to change afterwards.
- Feature lead time for the touched modules has moved in the right direction.
- Defect escape rate and change failure rate for those modules have moved in the right direction.
- The time a new engineer needs before making a safe change to the module has fallen.
- Knowledge of the module is no longer known by one worker approaching retirement or one contractor on a day rate.
- Maintenance cost for the module is flat or falling.
- At least one architectural limit has changed. Miquido calls the perpetual refactor the failure mode here, which is a project that delivers cleaner legacy code that remains legacy code.
- The cost of the work so far, including the parallel running and the review time, is noted. IshIR's study of refactoring cost shows that parallel cost is the expense stakeholders most often miss: the old system is still being paid for while the team improves it.
- The measurements are the ones the business already tracks. A slip in release cadence, a rise in incident volume or a maintenance line that has stayed flat says more than any count of files tidied.
- Every deliberate behaviour change is noted against the preservation statement, which has been checked against current system actions.
- The decision on the next system in the estate is taken with this work's real cost and real duration in hand.
Run the checks in the way they appear. The series of steps breaks quickly and at low cost: a programme stopped at authorisation costs a meeting, and a programme stopped at the quarter mark costs a year. Most of these questions can be answered from records you already hold and from the people who run the system, and once the answers are written down they become the record a supplier, an auditor or a successor team can act on.
Sources
- Complete Guide on Refactoring Legacy Code Without Breaking Anything
- Legacy Code Refactoring: Working Effectively with Legacy Code
- Refactoring Legacy Applications
- Overcoming the Challenges of Legacy Systems
- Legacy Code Refactoring Cost in 2027: Complete Guide
- RefactorBench: Evaluating Stateful Reasoning in Language Agents Through Code


