You've inherited a large legacy codebase with no tests and a bug to fix. How do you proceed?

SOLID & Design Patterns Scenario Advanced 3–5 Yrs5+ Yrs Scenario 1 min read

Quick Answer

Reproduce the bug and understand its blast radius before touching anything. Pin the current behaviour with a characterization test around the affected area (find or create a seam), make the smallest fix that passes, then add a regression test for the bug itself. Refactor only what you must to make the change safely — don't rewrite.

I don't start by changing code. First I get a reliable reproduction of the bug, ideally as a failing test. Then, before fixing it, I write characterization tests around that area — tests that just assert what the code does today, even the odd behaviour — so I have a safety net that tells me if I break something. Often I need to extract a seam first, like pulling an interface out so I can test it. Then I make the smallest possible fix, add a regression test for the actual bug, and stop. I don't do a big refactor in the same change — that buries the fix and multiplies the risk. I'll do small safe cleanups in the code I touched, nothing more.

  1. Reproduce — get a reliable repro, ideally as a failing test.
  2. Find a seam — a place you can sense behaviour without changing much (a method boundary, an interface you can extract). Michael Feathers' “Working Effectively with Legacy Code” is the playbook.
  3. Characterization tests — write tests that assert whatever the code currently does (even if it's weird), so you have a safety net.
  4. Smallest fix — change as little as possible; big refactors alongside a bug fix hide the fix in the diff and increase risk.
  5. Regression test — add one that specifically fails on the old bug.
  6. Leave it better — the boy-scout rule: small, safe cleanups in the area you touched, not a rewrite.