How Do You Improve Difficult Software Without Rewriting Everything?
The first three articles in this series built an argument in stages. It's a Simple Change. Why Isn't It Done Yet? looked at why apparently simple changes take longer than expected. Why Small Software Changes Become Expensive followed where the additional time and cost come from. If the Software Works, Why Fix It? asked when those costs, risks and constraints are significant enough to justify intervention.
That leaves the practical question this final article is about. Once an organisation accepts that its software is creating problems, what should it actually do?
The instinctive answers tend to cluster at two extremes: leave it alone, or replace it. Both are occasionally right. Neither is a good default. What follows is a more useful way to get from "this software is difficult" to a plan.
Improvement does not mean rewriting everything
The phrase "modernise the system" quietly smuggles in an assumption: that the system is one thing, with one condition, needing one response.
It almost never is. A long-lived codebase is a collection of areas in very different states. Some are stable and well understood. Some are awkward but rarely touched. A few sit underneath the business's most important and most frequently changed processes and make every change to them harder than it needs to be. Treating all of that as a single problem leads to a single, oversized answer.
The useful question is not "should we modernise this system?" It is "which areas are creating meaningful problems, and what is the proportionate response to each one?" The range of responses is wide: leave alone, protect, repair, refactor, isolate, rebuild, replace, buy or retire. Different areas of the same system will need different answers, and arriving at those answers is the real work.
The aim is not the biggest project. It is the right intervention.
Understand before deciding
Most organisations with difficult software already have a story about it. There is the "untouchable" module. There is "the thing we should really rewrite". There is the area everyone knows is the problem.
Sometimes those stories are accurate. Often they are built from reputation, one painful incident or the view of whoever last worked in that area. Decisions made on that basis carry a specific risk: the organisation spends serious money fixing the area with the worst reputation rather than the area creating the most cost.
Understanding the system properly is the alternative, but it is worth being precise about what that means. It does not mean documenting every class and method or reconstructing every historical decision. That is an open-ended project that delays the decision rather than informing it. Decision-useful understanding is narrower and more practical. It covers the things that determine what the options are:
the important business workflows and the code paths that support them;
the major dependencies between areas, including integrations, shared data and duplicated logic;
where knowledge and risk are concentrated, whether in a few people or a few fragile components.
Alongside those, the practical constraints matter: how the system is tested, how it is deployed, and what makes a change in one area ripple into another. Government guidance on moving away from legacy systems makes the same point from the public-sector side: understand the existing system, its constraints and the available options before planning what replaces it. The principle is simple. Establish how the system actually behaves and where the meaningful constraints sit before prescribing the solution.
Identify the areas that actually matter
"The codebase is bad" is a feeling, not a finding. It gives nobody anything to act on.
The first step towards something actionable is to stop thinking about the system as a whole and start thinking about it in parts: workflows, modules or services, integrations, the journeys that customers and operational staff actually use. Then, for each part, ask whether its complexity is producing consequences anyone cares about.
Three questions do most of the work:
Which areas consume disproportionate development or testing time relative to the size of the changes made to them?
Which areas change frequently now, or are expected to change frequently as the business evolves?
Which areas sit underneath processes that matter most: key customers, revenue, regulatory obligations or critical operations?
Where the answers overlap, you have found something worth examining. Where an area is complex but scores low on all three, you have probably found something to leave alone. Recurring defects and unusually wide regression testing are useful signals too, but they are signals of consequence, not of ugliness.
The objective is not to find every technical weakness. It is to identify where complexity is producing meaningful consequences.
Connect technical findings to business impact
Technical findings only become decisions when somebody translates them.
Consider the difference between two statements about the same code. The first: "This class is too large." That is true, perhaps, and completely useless to anyone who has to approve a budget. The second: "Payment logic is spread across several tightly connected components, so every change to pricing or payment rules requires wider investigation and regression testing than it should, and carries a real risk of inconsistent behaviour between the checkout and the refund process." That is something a technical director and a finance director can both reason about.
A useful finding has three parts that connect in a chain. It names the technical condition: tight coupling, duplicated logic, poor boundaries, knowledge held by one person. It describes the effect on development and operations: more investigation, wider testing, uncertain estimates, nervous releases. And it states the business consequence: higher cost of change, slower delivery of things the business wants, exposure for customers or operations, and declining confidence in making changes at all.
When findings are written this way, prioritisation stops being an argument between engineers and the business. Both are looking at the same chain and can see which links matter most.
Prioritise before changing anything
Finding a problem is not the same as deciding to fix it. Every intervention costs money and engineering attention, and both are finite.
Prioritisation needs to weigh several things at once: the business value and risk attached to an area, how much change the business expects to need there, and the cost, dependencies and risk of the available interventions. It also has to respect organisational capacity. A roadmap that assumes the whole engineering team can be redeployed for six months is not a roadmap; it is a wish.
This is where simple rules fail. "Highest risk first" ignores that some risks are cheap to mitigate and others ruinously expensive. "Easiest wins first" can spend months on tidying that nobody will notice. "Oldest technology first" confuses age with consequence. As Part 3 argued, a technically poor area that almost never changes may deserve nothing, while a moderately awkward area that the business will keep changing may deserve the most attention of all.
The question to hold onto is this: what sequence of work gives the organisation the best balance of value, risk reduction, effort and practicality?
Choose the appropriate response for each area
With the areas that matter identified and ranked, each one needs a response. These are the options, and the conditions under which each tends to make sense.
Leave it alone. The area is stable, rarely changes and creates little meaningful cost or risk. Choosing this is a deliberate decision backed by evidence, not neglect, and it frees budget for the areas that do need it.
Protect what exists. The behaviour should stay as it is, but the organisation needs more confidence around it. That might mean documentation so that fewer people depend on one person's memory, tests around fragile behaviour so that nearby changes can be made safely, or clearer boundaries and monitoring so that problems are noticed sooner. This is often the right first move for an important area nobody fully trusts.
Repair or refactor. The behaviour is broadly correct, but the structure repeatedly creates unnecessary cost, uncertainty or risk every time it is touched. The intervention should be targeted at the specific structure causing the problem, not at everything in the vicinity.
Isolate. The area is difficult mostly because it is too tightly connected to everything else, so its complexity spreads outwards. Putting a clear boundary around it, so the rest of the system talks to it through a defined interface, can limit how far that complexity affects the rest of the system without replacing the component at all. It also makes later replacement far easier if that turns out to be needed.
Rebuild, replace or buy. The evidence shows the existing component is no longer a sensible foundation for what the business needs now or expects to need next. This is not an admission of failure. Sometimes a rebuild, a replacement or an off-the-shelf product is simply the proportionate answer, and pretending otherwise wastes money on repairs to something that should go.
Retire. The functionality no longer creates enough value to justify maintaining it. Removing it reduces the surface area the team has to understand and test.
None of this is unusual thinking. Microsoft's guidance on migration strategies draws a similar set of distinctions, including retain as a legitimate choice for stable workloads with little expected return from change, and UK Government guidance describes staged approaches that improve or replace systems gradually rather than all at once. Neither argues that rewrites are wrong. Both argue that the response should fit the problem.
In practice, a single system will receive several of these answers at once. One area is retained. Another is protected with tests and documentation. A third is refactored. A fourth is replaced. A rewrite is one option, not the default, and so is leaving well alone.
Turn the decisions into a phased roadmap
Findings and recommendations, however good, are not a plan. The organisation needs an order of work, because it cannot do everything at once and should not try.
A useful roadmap makes three things clear: what should change and what should remain, why each piece of work matters in business terms, and what should happen first and what can wait. Behind that sits the sequencing logic: which pieces of work depend on others, which risks need addressing before further change is safe, and what the team can realistically absorb while still supporting customers and shipping the things the business needs this quarter.
Sequencing matters because the business does not pause. Revenue has to keep flowing, support has to keep working and the engineering team has a finite number of hours. A roadmap that ignores this is a fantasy, and a modernisation programme that treats the whole system as one open-ended project is where budgets and patience are lost.
One caveat belongs here. A roadmap built from an assessment gives direction and sequence; it does not give perfect foresight. Implementation produces new information, and sometimes that information changes a priority or a chosen response. That is not a failure of the roadmap. It is what the roadmap is for. The aim is to turn modernisation into a series of controlled decisions, each better informed than the last, rather than one commitment made in advance and defended regardless.
How the Codebase Assessment fits
Everything above is a decision process, and it needs evidence to run on. CodeOptimo's Codebase Assessment is designed to produce that evidence and the judgement that goes with it, in three groups.
Understand the system. System Documentation and Knowledge Capture establish how the important parts fit together and record the knowledge that currently depends on particular people.
Decide what matters. Impact & Risk Analysis and Modernisation Options identify where complexity is creating real consequences, and set out a proportionate response for each significant area.
Plan the work. The Prioritised & Phased Roadmap and the Modernisation Business Case turn those decisions into a sequence, with the cost, risk and expected value made explicit, including a baseline for the measurable costs associated with the current system.
The purpose is not to produce a catalogue of everything technically imperfect in the software. It is to provide enough evidence to decide what is worth changing, what can stay, what response makes sense, what should happen first and whether the investment is justified. Where it makes sense, CodeOptimo can also help implement the roadmap. But the assessment is designed to stand on its own, and it is just as useful if the work is carried out by your own engineering team or another delivery partner.
Changing the right things, for the right reasons, in the right order
This series started with a frustrated question: why isn't a simple change done yet? It ends with a more useful one: what, specifically, is worth doing about it?
Difficult software does not automatically require a rewrite, and it does not automatically require anything at all. What it requires is a proper look. Understand what you have, at the level needed to make decisions rather than to document everything. Identify where complexity is producing consequences that the business actually feels. Connect those technical conditions to cost, risk and constraint so that engineers and decision-makers are weighing the same things. Choose a proportionate response for each area. Then make the changes in a controlled order that the organisation can sustain.
Some areas will stay exactly as they are. Some will need protection. Some will need targeted improvement. Some will genuinely need replacing. A system handled this way does not become perfect, but it does become something better than that: understood, deliberately managed, and easier to change where change is needed.
You do not improve difficult software by deciding in advance to rewrite it. You improve it by changing the right things, for the right reasons, in the right order.
If that is the conversation you need to have about your own system, book an introductory call.