login as:
~/abapcraft.dev — code, crafted in SAP
florin@abapcraft:~/abap/posts/book-working-with-legacy-code $ cat README.md

Working Effectively with Legacy Code — Michael C. Feathers

The definitive guide to making untested, hard-to-change code safe to work with — without breaking what already works.

What the book is about

Written by Michael C. Feathers — who also contributed to Clean Code by Robert C. Martin — Working Effectively with Legacy Code addresses the problem that most developers face most of the time: changing code that has no tests, no clear structure, and no documentation beyond itself.

Feathers begins with a definition that reframes the problem entirely: legacy code is code without tests. Not old code. Not bad code. Code without tests — regardless of when it was written. This shifts the focus from the age or quality of the code to the absence of the safety net that makes change safe. With that definition, much of the code in production today is legacy code.

The book is a catalogue of techniques for getting that code under control: how to understand it, how to add tests without breaking it, and how to refactor it safely once the tests are in place.

The golden master and characterisation tests

Before changing a single line of legacy code, you need to know what it currently does. Not what it should do — what it actually does, including all its quirks and side effects. This is what characterisation tests capture.

A characterisation test does not assert correct behavior — it asserts current behavior. Run the code, observe the output, write a test that pins that output. Now you have a golden master: a fixed record of what the system does today. Any change that breaks a characterisation test is a change that altered behavior — intentionally or not.

This discipline is what makes refactoring legacy code safe. The golden master is the safety net you build before you touch anything.

Seams and dependency breaking

The central concept of the book is the seam — a place in the code where you can alter behavior without editing the code in that place. Seams are how you get legacy code under test: find the seams, use them to substitute dependencies, and write tests against the isolated unit.

Feathers describes a set of dependency breaking techniques for creating seams where none exist:

Adapt Parameter — when a method depends on a concrete type that is hard to control in a test, wrap it in an interface or adapter. The production code passes the real object; the test passes a substitute.

Encapsulate Global References — global variables and static calls are among the hardest dependencies to break. Wrapping them in a class or method creates a seam where a substitute can be injected.

Extract and Override Call — extract a method call into a virtual method, then override it in a test subclass. This gives the test control over behavior that was previously hardwired.

Mock objects and test harnesses

Mock objects are a primary tool for breaking dependencies in legacy code. When the real dependency — a database, a service, a file system — cannot be used in a test, a mock replaces it with a controllable substitute that records what was called and returns what the test needs.

General test harnesses complement mocks by providing the scaffolding to run a class or method in isolation: setting up only what is necessary, controlling inputs, and observing outputs. Together, mocks and harnesses make it possible to test code that was never designed to be tested.

Understanding changes

Before making a change to legacy code, Feathers recommends understanding the effect of the change: which parts of the system will be affected, and which tests need to cover them. This analysis — tracing the ripple of a change through the codebase — prevents the most common mistake in legacy work: changing something in one place and unknowingly breaking something in another.

Personal note

This book is an excellent reference for anyone working with legacy code — which, by Feathers’ definition, is most of us most of the time. The techniques are concrete, the examples are realistic, and the golden master / characterisation test approach alone is worth the read.

It is not the easiest book to follow cover to cover. Some sections are dense, and the dependency breaking catalogue can feel repetitive when read sequentially. The book works better as a reference: identify the problem you have, find the relevant technique, apply it.

Among the books in this series, it occupies a unique position. The others teach you how to write good code from the start. This one teaches you how to recover when the start did not go well — which is often the more urgent problem.