Part 14 · 1 chapters · ~8 min

Working Effectively with Legacy Code

Michael Feathers on changing code that has no tests: legacy defined as untested, cover and modify instead of edit and pray, the change algorithm, seams for substituting dependencies, characterisation tests that pin current behaviour, and sprouting or wrapping new logic.

16

Working Effectively with Legacy Code

code
// a characterisation test, then a sprout: legacy eligibility code in an Angular service
// 1. pin what it does today, including the odd case
it('characterise: settled balance of 0.004 naira is treated as eligible', () => {
  expect(legacy.isEligibleForLetter({ balance: 0.004, status: 'SETTLED' })).toBe(true);    // surprise: floating point
});

// 2. sprout the new rule as a small tested function, in kobo
export const eligible = (l: { balanceKobo: number; status: 'SETTLED' | 'PENDING' }) =>
  l.status === 'SETTLED' && l.balanceKobo === 0;

// 3. call it from the old code; the characterisation test now fails on purpose,
//    and the team decides the new behaviour explicitly (0.4 kobo is not zero: route to operations)
situationtechnique
a service constructs its own HTTP client insideobject seam: inject the client through the constructor
a module imports a singleton you cannot run in testsmodule seam: substitute at import with the test runner's module mocking
a 400-line method needs one new rulesprout method: new tested function, one call added
you need logging or checks around existing behaviourwrap method
nobody knows exactly what the code doescharacterisation tests first, then decide
your story in these terms
The KYC and verification module refactor in Angular is a legacy-code story. Telling it with this vocabulary (what you characterised, where the seams were, what you sprouted) makes it precise and credible, and keeps the scope honest.
WORKING EFFECTIVELY WITH LEGACY CODE
Michael Feathers, 2004: getting untested code under test so it can be changed safely
swipe the figure sideways, or tap expand for full screen
1/6
Legacy means untested
Feathers defines legacy code as code without tests. Without tests you cannot know whether a change broke something, so every change is slow and frightening. The definition shifts the conversation from "old code is bad" to "code we cannot get feedback on is risky".