FDE PulseFDE jobs open 434New in the last 7 days 27
VI

The newspaper of the Forward Deployed Engineer

Books & courses

Working Effectively with Legacy Code: the 2004 book that helps FDEs add AI features to untested code

The customer system you are about to wire an LLM into almost certainly has no tests. Michael Feathers explained how to handle that more than twenty years ago.

Cover of Working Effectively with Legacy Code
Working Effectively with Legacy Code · Michael C. Feathers · Cover: Open Library

In brief

  • Feathers defines legacy code as code without tests. The age of the code has nothing to do with it.
  • Before changing anything, record the current behaviour with characterization tests. AI can write them for you, and some tests only need to exist for a while.
  • A seam is a place where you can change behaviour without editing in that place, and it is where the LLM goes in.
ShareLinkedInFacebookX

Michael Feathers says he is best known for a single definition: legacy code is code without tests. The code may have been written last year. Until it has tests, it is legacy.

By that measure, most codebases a forward deployed engineer opens in the first week at a customer site are legacy. Yet the job is often to add an AI feature, such as summarising case files or classifying tickets with an LLM, to a system you did not write, that nobody dares touch, and that the customer will not allow to break.

That is why this book, published in 2004, belongs on the desk of anyone who wants to work as an FDE. It says nothing about AI, but it teaches the hardest part of the job: changing other people’s code without breaking what already runs.

An old book for a new problem?

Working Effectively with Legacy Code was published by Pearson on 22 September 2004 as part of the Robert C. Martin Series (ISBN-13 978-0-13-117705-5). The publisher sums up its aim as helping programmers deal with inherited code without rewriting it all, which is very expensive.

That outlook matches the reality of deployment work. You will rarely be invited to propose a rewrite of a customer’s system, and almost never to carry one out in a few weeks. What you need is a safe way to work your way in.

One obstacle is worth knowing upfront. The book’s examples are in Java, C++ and C#, and it assumes you can read UML notation. If you mainly write Python or TypeScript, read it for the ideas and translate the examples into your own language yourself. Do not read it to copy code.

Idea one: missing tests are the real risk

The core of the book is writing tests so that changes do not accidentally alter how the program behaves. Feathers is blunt: without tests you are in serious trouble; with them you are in the “golden zone”.

His most practical advice is not to try to cover the whole system with tests. In poorly tested code, write tests for exactly the part you are about to change. An FDE working against the clock should treat this as a working rule: tests follow the area of change, not a coverage target.

Idea two: record what the code does before you change it

Imagine a customer has a Java function, routeTicket(), several hundred lines long, that uses a chain of keyword-based if statements to assign priority to support tickets. They want to replace the priority logic with an LLM. The first step is not calling a model.

The first step is to take a batch of real tickets, run them through the current function and write tests asserting exactly the output it produces today, including results that look like bugs. Feathers calls these characterization tests, and describes them as a record of the code’s current behaviour, not its correct behaviour.

Feathers also sees this as work AI does well. He suggests accepting that some tests are temporary, written to understand the code or to support a change and then thrown away. That lets you ask a coding assistant to generate descriptive tests in bulk without worrying about maintaining them forever.

Idea three: find a seam to plug the LLM into

The most valuable concept in the book is the seam. Martin Fowler quotes Feathers’ definition: a seam is a place where you can alter a program’s behaviour without editing in that place. Feathers likens it to the seam on a garment, a natural break point where one component can be swapped for another.

Back to routeTicket(). You extract the priority logic into a PriorityClassifier interface and pass it in through the constructor. The default implementation contains exactly the old if chain, so every characterization test stays green. That is your seam.

From there you write a second implementation that calls an LLM, plug it into the same seam and switch it on gradually for each group of tickets. In tests you replace it with a fake that returns fixed results, so the suite depends on neither the model nor the network. Not one line of the old routing logic is changed.

Who should read it, and in what order

The book suits developers with two to eight years of experience who have had to change someone else’s code with shaking hands. Read the Legacy Seam page on Martin Fowler’s bliki first to grasp the concept, then read the sections on testing legacy code and on seams carefully. Skim the rest and come back when you hit the matching situation.

In FDE job descriptions, phrases such as integrating with existing systems or working in customer codebases describe exactly this skill. On your CV, describe a time you added a new feature to a module with no tests: how you wrote the characterization tests, where you chose the seam, and how nothing broke.

Models will keep changing every quarter. The untested, several-hundred-line function at the customer site will still be there, waiting for someone who knows where to find the seam.

3 sources
Read next on the roadmap · Stage 2: Broad engineeringKleppmann and Riccomini update DDIA: storage is moving to object stores, and the trade-offs are moving tooMore and more data systems keep their data in object stores, not on a server's local disk. The old trade-offs are all still there, just in different places, and this book helps you find them.