Just refactor the legacy system
Why legacy systems rarely fail because of ugly components — they fail because of unknown dependencies, missing tests, and undocumented business knowledge.
It sounds like a reasonable task.
“That’s legacy. Let’s just refactor it.”
A little cleanup. Split a few large components. Rename a few services. Move a few files around. Then the team is fast again.
Unfortunately, that’s rarely refactoring.
More often it’s reverse engineering with good intentions.
Because legacy systems rarely fail just because the code looks ugly. They fail because of unknown dependencies, missing tests, undocumented business knowledge, edge cases nobody remembers adding, and requirements nobody can state cleanly anymore.
If you underestimate that, you’re mistaking refactoring for tidying up.
And tidying up doesn’t help when nobody knows which wall is load-bearing.

The fallacy
Section titled “The fallacy”The fallacy starts with the word “just.”
“Just refactor it.”
That makes it sound like the problem is already understood. As if all that’s left is execution. As if you just have to give the team enough time to make the code prettier.
But legacy code is rarely just messy.
Legacy code is often code whose behavior is used in production every day, while the reasons behind that behavior are no longer fully known to anyone.
Why does this exception exist? Why is this field set in three different places? Why can’t this status be deleted? Why does this component call that API directly? Why is this validation logic sitting in the template? Why does the same piece of state exist in the store, the form, and the service?
The answer is often not visible in the code itself.
It lives in old tickets, forgotten bugs, customer workarounds, business edge cases, data migrations, historical release incidents, or in the memory of someone who has since moved to a different team.
That’s not ugly code.
That’s system behavior nobody fully understands anymore.
”It needs to work better” is not a requirement
Section titled “”It needs to work better” is not a requirement”Many legacy initiatives start with sentences like:
“This needs to work better.”
Or:
“The teams need to move faster.”
Or:
“The old UI needs to be refactored.”
The problem: these sentences describe dissatisfaction, not a concrete change anyone can work toward.
What does “better” mean?
Faster load times? Fewer regressions? Shorter feature lead time? Better testability? Less coupling? Easier onboarding? Safer releases? Less manual checking? Less fear of making changes?
Without that clarity, “refactoring” turns into a blank screen everyone projects their own meaning onto.
To developers, it means paying down technical debt. To product owners, it means fewer bugs. To management, it means higher velocity. To QA, it means less manual testing. To operations, it means lower production risk.
Everyone says “refactoring.” Everyone means something different.

Without tests, refactoring is often reverse engineering
Section titled “Without tests, refactoring is often reverse engineering”Refactoring doesn’t mean:
“I keep changing the code until it looks nicer.”
Refactoring means:
“I improve the internal structure without changing observable behavior.”
That requires knowing two things.
First: which behavior has to be preserved? Second: how do we know it’s still preserved?
When either one is unclear, refactoring gets risky.
Without tests, every change is a claim. Without concrete business examples, every simplification is a guess. Without known edge cases, every cleanup is a potential bug. Without monitoring, some failures only show up in production.
In systems like that, the first step isn’t the big cleanup.
The first step is building a safety net.
Sometimes that means unit tests. Sometimes integration tests. Sometimes characterization tests. Sometimes golden-master tests. Sometimes just logging. Sometimes a catalog of concrete business examples. Sometimes it’s simply: first understand what the system actually does.
That feels slow.
But it’s faster than flying blind and hoping for the best.

Large components are a symptom, not the cause
Section titled “Large components are a symptom, not the cause”Sure, a 2,000-line component is a problem.
But it’s rarely the actual cause.
A large component is often just the place where a lot of unresolved decisions become visible:
- UI state and business state are mixed together.
- API DTOs flow straight through to the template.
- Validation logic is scattered across multiple places.
- Side effects happen inside lifecycle hooks.
- Permissions were never centralized.
- Error handling is implicit.
- Data gets mutated locally.
- Business logic has drifted into the UI layer.
- Edge cases were never modeled.
If you only split the file, you spread the problem around.
Then there’s no longer one large component.
Then there are eight smaller files that still form the exact same unresolved system.
That looks better.
It isn’t automatically better.

Refactoring needs a strategy
Section titled “Refactoring needs a strategy”Good legacy refactoring doesn’t start with renaming things.
It starts with diagnosis.
Which parts change often? Which parts are business-critical? Which parts break often? Which parts make people nervous? Which parts block new features? Which parts are just ugly, but stable? Which parts genuinely have to be preserved? Which parts can be replaced?
After that, you need an order of operations.
Not every piece of technical debt matters equally. Not every ugly spot deserves attention right away. Not every rewrite is wrong. Not every refactor is worth doing.
A usable strategy answers at least four questions:
- What risk are we reducing?
- What behavior are we securing before we touch anything?
- What boundary are we trying to make visible?
- How will we know the change actually helped?
Without these questions, refactoring is just busywork.
With them, it becomes modernization.

Decision-makers have to enable refactoring
Section titled “Decision-makers have to enable refactoring”A common mistake is treating refactoring as purely a developer task.
“You’re the engineers. Just do it on the side.”
That rarely works.
Not because developers drag their feet. But because real legacy refactoring depends on decisions.
Which business behaviors are critical? Which old edge cases are safe to drop? Which workarounds still matter? Which risks are we willing to accept? Which areas are we allowed to isolate? Which feature work are we willing to slow down in the meantime? Which metrics actually matter?
A development team can’t answer these questions alone.
It can lay out the options. It can explain the risks. It can propose technical paths. It can plan incremental steps.
But if nobody clarifies business priorities, no plan emerges.
Only pressure does.
And pressure on legacy systems rarely produces good architecture.
It produces more shortcuts.

“Teams need to work faster” is often the wrong diagnosis
Section titled ““Teams need to work faster” is often the wrong diagnosis”When teams slow down inside legacy systems, that’s not automatically about the team.
Sometimes the system itself is what slows the team down.
Every change needs too much context. Every bug fix has side effects. Every test is manual. Every small adjustment triggers a round of coordination. Every release feels dangerous. Every new hire needs months to become productive.
At that point, “work faster” isn’t a strategy.
It’s telling someone to drive faster on a broken road.
A good modernization effort doesn’t just ask:
“Why is the team delivering so slowly?”
It also asks:
“What structural conditions make fast, safe delivery unlikely in the first place?”
That’s a more uncomfortable question.
But usually the more useful one.

Sometimes a targeted rewrite is cheaper
Section titled “Sometimes a targeted rewrite is cheaper”“No rewrites, ever” is just as dangerous a rule as “rebuild everything.”
Of course big-bang rewrites are risky. Of course teams routinely underestimate the hidden business logic buried in the old system. Of course a full rebuild is rarely as clean in practice as it sounded in planning.
But sometimes cosmetic repairs cost more than a targeted replacement.
If an area is well understood from a business perspective, has clear boundaries, and can’t realistically be made testable after the fact, a small, scoped rewrite can be cheaper than months of surgery around the edges.
The real distinction isn’t refactor versus rewrite.
The real distinction is controlled versus uncontrolled.
A complete big-bang rewrite is dangerous. A targeted replacement of a clearly bounded slice can make a lot of sense.
The strangler pattern, not a heroic rebuild
Section titled “The strangler pattern, not a heroic rebuild”Legacy modernization gets dangerous once it turns heroic.
“Let’s just build this the right way this time.”
That sounds good.
Until the new system has to recreate every old edge case. Until running both systems in parallel becomes unavoidable. Until data migration turns out more complicated than expected. Until feature pressure comes back. Until the old system keeps living anyway. Until the team has two systems to maintain.
Working incrementally is less dramatic.
But usually more professional.
A new slice next to the old system. A clear boundary between them. A routing or integration point. A safe cutover. A measurable benefit. Then the next slice.
That’s less romantic than a big rebuild.
But legacy systems rarely reward romance.
They reward small, safe steps.

The better sentence
Section titled “The better sentence”Instead of saying:
“You can just refactor legacy code and make it nice.”
we should say, more precisely:
“We first need to understand which behavior has to be preserved, which risks are slowing us down, and which boundary we can safely draw next.”
That sounds less snappy.
But it’s more honest.
Legacy work isn’t just code work. It’s analysis, building a safety net, architecture, and decision-making.
Anyone who demands refactoring also has to enable the conditions for it:
- time for diagnosis
- access to business knowledge
- decisions about old edge cases
- tests or some other safety net
- small modernization slices
- clear priorities
- accepting that not everything ugly needs to be fixed first
Refactoring isn’t a magic wand.
It’s surgery.
And with legacy systems, you’d better know whether you’re cutting through skin, fat, or an artery before you make the first incision.