Guide

Legacy code is code without tests you trust. Every edit is a bet until you pin what it does today. Refactoring improves structure without changing observable behavior, so the next behavior change is safe.

Big-bang rewrites usually fail. They freeze features for months, rediscover the same edge cases, and ask the business to wait. Prefer incremental improvement that keeps shipping.

Concept

Refactoring means a behavior-preserving transformation. If users can tell you changed output, you changed behavior. Martin Fowler's discipline: many tiny steps, always green.

Why this is a craft, not a cleanup week

You will spend more time changing code you did not write than writing greenfield. The skill is risk management: lock behavior, open a seam, take tiny green steps, then migrate behind a flag. Dedicated "cleanup weeks" fail when they lack a forcing product need. Opportunistic tidying on the path of real work, plus a scheduled migration when the platform is on fire, is the durable mix.

SDE2s leave the module on the critical path better than they found it. Multi-quarter stranglers need a plan, stakeholders, and a dashboard — the same ownership you would bring to a feature launch.

A rewrite is rarely the first move. It can win when the module is a prototype with no users yet, smaller than the test harness would be, or under a hard technology mandate with isolation already in place. Even then, sit it behind a facade and parallel-run. If you cannot characterize today's behavior or route gradually, you are not ready to rewrite.

In interviews, describe a strangler or expand/contract you drove. Emphasize characterization tests and separated PRs. Be honest about a rewrite you would not do again. Judgment matters.

Concepts in this section

Read them in order. Each one builds on the one before.

StepConceptWhat it answers
1Characterization testsHow do you lock today's behavior before you touch it?
2Seams and dependency breakingHow do you test code that constructs everything with new?
3Tiny refactor stepsHow do you change structure without mixing in behavior?
4Incremental migrationsHow do you replace a system without a rewrite?
5Selling tech debtHow do you fund and track the work?

Common mistakes

  • Refactoring without tests. "I am careful" is not a strategy.
  • Behavior changes hidden inside cleanup PRs. Reviewers miss them; bisect lies.
  • Boiling the ocean. Pick the module on the path of the next feature.
  • Rewrites with no seams and no gradual cutover. Big-bang day is how you learn Feathers the hard way.

Key takeaways

  • Legacy means untested. Start by pinning today's behavior.
  • Open a seam, then tidy in tiny always-green steps, then change behavior in a different PR.
  • Prefer strangler, expand/contract, parallel run, flags, and codemods over rewrites.
  • Sell the work in incident, speed, and risk language. Stop when the next feature is easy.

Further learning

Fowler's "Workflows of Refactoring" talk names litter-pickup, comprehension, and preparatory refactoring.

Martin Fowler @ OOP2014 "Workflows of Refactoring"heise conferences
Refactoring (Martin Fowler)Refactoringrefactoring.com StranglerFigApplication (Martin Fowler)Strangler Figmartinfowler.com Working Effectively with Legacy Code (Michael Feathers)In this book, Michael Feathers offers start-to-finish strategies for working more effectively with large, untested legacy code bases. This book draws on material Michael created for his renowned Object Mentor seminars…informit.com