Legacy code refactoring: change your code with confidence again, without pausing the roadmap

We work inside your code with your team and make it easy to change again, step by step: no rewrite from scratch, no roadmap on hold.

The software works, but every change costs more than the last one

Almost nobody calls us because the code is ugly. They call when the product is selling, the team is capable, and yet every change still takes weeks and brings a few regressions with it.

  • Every feature takes longer than the one before it
  • Small changes produce unexpected regressions in distant parts of the system
  • There are parts of the code the team would rather not touch
  • There are few automated tests, and every release depends on long rounds of manual testing
  • Only one or two people really understand the critical modules
  • A product change forces you to touch many modules that have nothing to do with it
  • The team starts talking about rewriting everything from scratch

This is friction, and it lands precisely on the changes that create business value, making them a little more expensive every month

Incremental refactoring, inside your own code

Legacy code refactoring makes a production system easy to change again: it reshapes the structure without altering the behaviour, in small steps protected by tests. We don't rewrite the system and we don't work on a separate copy. The refactoring happens in the code that goes to production, one small reversible step at a time, while the team keeps shipping features; every step leaves the system working and a little easier to change than before.

The system you have embeds years of domain knowledge, including edge cases nobody remembers handling any more. An incremental engagement preserves that knowledge; a rewrite has to rebuild it from scratch, and almost always at a higher cost than expected.

We prefer to be clear about timing from the start: expect the first measurable progress after three to six months, not before. The full engagement takes longer, depending on how much debt there is and where it sits; the exit plan sets its closing conditions from day one.

Two modes, depending on the damage

Not all debt calls for the same response. That's why we work in two different modes, and we don't decide between them upfront.

The main mode

Scope team

When debt is blocking delivery in central areas of the product

A pair of QMates developers joins some of your own developers to form a small team of experts with one precise goal: get delivery moving again where it is currently stuck. As it works on the code, the scope team passes the techniques on to the rest of the team, who see them applied to their own system before using them independently.

The light-touch mode

Opportunistic refactoring

When the damage is contained and the system still keeps up with the pace of the product

Teams keep shipping features and, before adding one, improve the code that feature needs to touch. Refactoring doesn't become a separate project with its own budget: it follows the roadmap and focuses where the product is heading.

The choice between the two modes comes from the technical debt assessment, the first step of the engagement. It measures where the debt really hurts and how much damage it is doing; from there we decide whether a scope team is needed or opportunistic refactoring is enough.

The techniques we use, and what they are for

These are techniques well known to anyone who has worked on legacy code for years. The difference lies in the order in which they're applied and how we choose where to apply them.

  1. 01

    Characterisation tests as a safety net

    Before changing part of the system, we capture its current behaviour with tests that describe it exactly as it is, even where it's wrong. They don't say what the system should do, they say what it does today, and they flag immediately if a change alters it unintentionally. This is the technique Michael Feathers describes in Working Effectively with Legacy Code, and it's the prerequisite for everything else.

  2. 02

    Hotspot refactoring

    We pay down debt where the code changes most. We cross-reference change history with code complexity and act first on the hotspots, the few areas where high complexity and frequent change overlap; a complicated module nobody has touched in years can stay as it is.

  3. 03

    Module boundaries and coupling

    When a product change cuts across ten modules, the problem lies in the boundaries. We make the boundaries between parts of the system explicit, cut the dependencies that cross them, and move responsibilities to where the domain says they belong, so that a change touches only one place.

  4. 04

    Strangler Fig for the monolith

    When part of the monolith needs replacing, there's no single cutover day. The new code grows alongside the old behind a clear boundary and takes over one function at a time, until the old code is no longer needed; the monolith keeps working throughout.

  5. 05

    Architecture only where it is needed

    Microservices, events or new layers are not a goal in themselves. We change the architecture when a structural constraint stops the product from evolving, and only then: a modular monolith with clear boundaries can hold up well for years.

How we measure progress

At the start of the engagement we agree a handful of metrics with you, tied to what costs too much today, and track them over time. Which metrics to choose depends on the system and on what is holding the business back; for example:

  • the lead time for changes
  • regressions that reach production
  • the time a release takes
  • the time it takes to get a new feature into production

We don't promise numbers before we've seen the code. We do promise to measure from the start, so that progress, or the lack of it, is visible to everyone rather than left to gut feeling.

Where we've already done it

Two real engagements, in client code and alongside their teams.

SureVIVE

The context
Mission-critical software for coordinating emergency rescue, running day and night, where an architecture no longer fit for purpose and parts of the legacy code were holding back the growth of both the product and the organisation.
What we did
A refactoring engagement that improved testability and design ahead of the product features that depended on them, working alongside their team.
The result
Technical debt down 15%, and features that were previously out of reach now deliverable, opening up new sales opportunities for the product.
Read the case study

Zextras

The context
A B2B collaboration suite built by more than three distributed teams, in an engineering organisation that had grown from 5 to around 30 developers, with delivery that kept stalling.
What we did
Over twelve months: a four-day course on technical practices for each team, and a QMates pair embedded in the core team, who helped set up the feature teams and led part of the refactoring needed to carve out their slice of the codebase.
The result
Bugs down 15% and cycle time improved; lead time stayed unchanged because releases follow a fixed cadence.
Read the case study

The team carries on without us

A successful refactoring leaves behind better code and a team that no longer needs us. The techniques are passed on to the team as we work together, pairing on real code, and our presence tapers off as the team starts using them on its own.

The exit plan is decided at the start

  1. 01We agree which skills need to stay in the team and how we'll know they've stuck
  2. 02We scale back our presence in stages, as the team takes ownership of the refactored areas
  3. 03We leave behind tests, boundaries and metrics the team knows how to read and maintain
  4. 04We close the engagement when the team carries the refactoring forward as part of everyday work

When we don't recommend a rewrite, and when we do

The question we get asked most often is whether the system should be rewritten. We almost always say no, and not on principle: a rewrite costs more than it appears to, because it also has to reconstruct the knowledge hidden in the existing code.

We don't recommend a rewrite when

  • the system is in production and generating value, even if changing it is hard work
  • the problem is concentrated in a few areas rather than across the whole codebase
  • the main pain is a lack of tests or knowledge, which a rewrite doesn't solve
  • the business can't afford months with features on hold, or two systems to maintain in parallel

We consider a rewrite when

  • the technology no longer receives security updates and has become a risk to service continuity
  • coupling is so pervasive that no part can be isolated, and every change consistently costs more than the value it brings
  • the business changes radically (a new model, an acquisition, a new platform) and the existing system is no longer needed, regardless of its quality

Even in these cases, the replacement happens piece by piece, using the Strangler Fig pattern, not in one go. Our guide sets out what to weigh up before deciding: when to live with legacy software, when to evolve it and when to rewrite it.

What we don't do

  • We don't rewrite everything from scratch as a first choice
  • We don't put the roadmap on hold to launch a refactoring project
  • We don't turn refactoring into a cloud migration for its own sake
  • We don't offer blanket guarantees: we prefer small, reversible steps verified by tests
  • We don't just train: we write code alongside your team
  • We don't make you dependent on us

We intervene where change costs the most, and stop when the team can carry on without us

It's the right time if

  • The product is growing, but the code is holding back every new feature
  • You're weighing up a rewrite and want to know if a less risky path exists
  • You've inherited a legacy system the team struggles to understand and change
  • Technical debt always loses out to features, and keeps growing in the meantime

If instead the problem is a single, urgent, well-defined technical issue, the quicker route is Targeted Intervention.

Frequently asked questions

Do you also work on code with no tests?
Yes, and it's the most common case. We start with characterisation tests, which describe the system's current behaviour even where it's wrong and become the safety net for changing the code without first having to understand every detail. The tests grow together with the refactoring, starting from the areas that get touched most often.
Do you write code or run training?
We write code. We work in your repository, pairing with your developers, on real features and real refactoring, and the techniques are passed on to the team as the work happens. When a dedicated training session is useful, we run it alongside the work on the code, not instead of it.
How does refactoring coexist with feature development?
Refactoring happens inside delivery, not instead of it. With opportunistic refactoring, we improve the code the next feature needs to touch; with a scope team, we unblock an area while the rest of the team keeps shipping. In neither case does the roadmap stop.
Do you work on monoliths too?
Yes. Often a monolith doesn't need splitting up; it needs to become modular, with clear internal boundaries. Where a part genuinely needs replacing, we use the Strangler Fig pattern: the new code grows alongside the old and takes over one function at a time, while the monolith keeps working.
How long does a legacy code refactoring engagement take?
Expect the first measurable progress after three to six months, not before. The overall duration depends on how much debt there is and where it sits, and the exit plan, with the conditions for closing, is defined at the start.
Where do we start?
With the technical debt assessment, which produces a prioritised map of the debt and an action plan, and also indicates which refactoring mode is needed. If the team prefers to take that plan forward on its own, it can.

Is legacy code holding back every new feature?

Let's begin with a free call: tell us about your system and we'll tell you whether an assessment is the right place to start

We use your data to respond to your request. Read our Privacy Policy.