Most Anticipated Books of 2022
Jump to ratings and reviews
Book Cover

Robert C. Martin Series

Working Effectively with Legacy Code

Get more out of your legacy systems, more performance, functionality, reliability, and manageability.Is your code easy to change? Can you get nearly instantaneous feedback when you do change it? Do you understand it? If the answer to any of these questions is no, you have legacy code, and it is draining time and money away from your development efforts.

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, techniques Michael has used in mentoring to help hundreds of developers, technical managers, and testers bring their legacy systems under control.

The topics covered include:

Understanding the mechanics of software change, adding features, fixing bugs, improving design, optimizing performance
Getting legacy code into a test harness
Writing tests that protect you against introducing new problems
Techniques that can be used with any language or platform, with examples in Java, C++, C, and C#
Accurately identifying where code changes need to be made
Coping with legacy systems that aren't object-oriented
Handling applications that don't seem to have any structure

This book also includes a catalog of twenty-four dependency-breaking techniques that help you work with program elements in isolation and make safer changes.

464 pages, Paperback

First published September 1, 2004

Michael C. Feathers

Michael C. Feathers

6 books248 followers

Loading...

Ratings & Reviews

Community Reviews

5 stars
1,734 (41%)
4 stars
1,591 (37%)
3 stars
666 (15%)
2 stars
166 (3%)
1 star
51 (1%)
Profile Image for Erika RS.
670 reviews168 followers
December 24, 2012
We wise programmers know that code should be written to minimize dependencies, inject those that are necessary, use interfaces, be tested from the start, etc. etc., and we always write our new code like that (right?), but our good habits and wisdom don’t matter when working with legacy code. You don’t have the option of going back in time and smacking some sense into the original author (who may have been your younger, more foolish self).

Given that we have to deal with legacy code, how can it be made easier? The fundamental premise of Working Effectively With Legacy Code is that you cannot work effectively with code unless it is covered by tests. When working with untested code you must be extremely careful, and even then the results are uncertain.

As a result, the bulk of this book is dedicated to techniques for helping get legacy code under test (specifically, fast running unit tests). Many of the techniques used for working with legacy code are well known testing or refactoring patterns, but Feathers puts them in a new light which highlights some of the special difficulties of working with legacy code.

Unlike code that was written with tests in mind, legacy code can be notoriously hard to test. The main enemy of getting tests in place is complex dependencies. To test a piece of code, you may have to create many other configuration objects, and some of those may be impossible/expensive to create in tests (e.g., live network connections). Even worse, you may not even have the hooks to create those objects because they are created deep within the code.
This brings up what Feathers calls “The Legacy Code Dilemma”:
When we change code, we should have tests in place. To put tests in place, we often have to change code.

To get around this dilemma, Feathers introduces the reader to low risk ways to introduce tests into existing code. Because these changes still have some risk, he recommends only adding tests to code you are working on. Don’t just go and start introducing tests everywhere because that requires changing code and may introduce bugs.

Feathers presents a catalog of techniques for introducing tests and breaking dependencies. I won’t go into them in detail (there are a lot). I will note that I am not that fond of the format of the chapters. Feathers names each chapter as if it were a question in a FAQ and then uses the chapter to answer that question. This will probably be great when I am trying to do something and think, “Didn’t that book have something to say about that?”, but it feels kind of hokey when just reading it as a book.

There are some common themes throughout this book. Dependency breaking is one. Another is that sometimes you have to make code more ugly to get it under test. You may have to add partial interfaces or classes that exist only to break the dependencies. Feathers gives two pieces of advice to get over that: it’s better to have tested code than untested code, and often the ugliness is an intermediate stage in unearthing the deeper structure of the code.

This book is not all about testing, however. Feathers does spend some time talking about techniques for understanding legacy code and discovering where to change it. In fact, those were some of my favorite chapters since I find getting started on understanding complex code bases to be rather intimidating. Chapters 11, 12, 16, and 17 discuss techniques for understanding code. Feathers talks about different ways of increasing understanding including sketching effects, taking notes about code, scratch refactoring, and telling the story of the system. I have found the effect sketching to be particularly useful in my day-to-day work.

Overall, this was a valuable read for anyone who has to understand and change large, confusing, delicate code bases (and what code base that has been around awhile isn’t?). The structure of the book made it repetitive at times, but the value of the contents outweighed the occasional repetition.
403 reviews60 followers
Edited July 9, 2012
This book should be considered a required companion book to Martin Fowler's Refactoring. Refactoring is about slowly and progressively turning ugly code into well-designed code. I'd read Refactoring, and tried its techniques, but I just couldn't figure out how to make it work for my purposes. I knew refactoring was based on having a robust suite of regression tests. Let's face it, most ugly code lacks such a suite of tests. If you want to refactor something and you don't have a test, you need to write one. But try writing tests for said ugly code! Good luck, and I'll see you when you've lost all your hair.

That's where this book comes in. This, I think, is what I need to make refactoring work for me. Its title is a little deceiving though. It's not really about working effectively with legacy code--it's about how to make legacy code testable. TDD and refactoring take it from there. Making legacy code testable is really the tough part, and this book does a great job of helping you tame it. It can be a messy process. A lot of this book feels like hacks. But it's way better than the alternative, traditional approach, which this book calls "edit and pray."

Since this book is geared toward traditional languages, C, C++, and Java, much of it is about showing you how to overcome the limitations and barriers of these (inferior, IMO) languages. Most of these stupid problems don't exist in newer languages. But they're still quite good to know. Especially important is some of the concepts this book introduces, such as sensing, separating, seams, interception points, pinch points, fakes, and mocks. Some of the techniques are quite powerful and useful everywhere, such as sprouting, wrapping, scratch refactoring, characterization tests, and sensing variables.

Some of this book is a little hard to follow, especially if you don't like or care for C++ and Java. This is partly because he uses actual production code from various projects as his examples. While it make it harder to follow, this adds a lot more realism to the book, which you'll appreciate if you're like me and have read many programming books with contrived and unrealistic examples.

Unless you have legacy code with a full suite of regression tests (very unlikely), don't even bother reading Refactoring unless you plan to read this book afterward.
Profile Image for Bill.
202 reviews69 followers
October 15, 2018
This has become a Legacy Book, unfortunately. The title is also misleading. It doesn't cover technical debt, evolving architecture, replacing a dead library, or other topics you might expect around the subject of long-term legacy projects. Instead, it focuses entirely on making changes safely by small steps, which comes down to adding unit tests.

Having added such tests to a legacy codebase, this is admittedly a tough task, especially to know where to begin, but most of the basics could be covered in a blog post. Now, I'm sure these were revolutionary ideas when this book emerged, but reading it in 2018 felt quite pointless. TDD may not be followed habitually everywhere but I haven't seen a lot of projects in the last ten years without at least some unit tests.

Don't bother with this one. If you're new to unit tests and refactoring, just read a classic on one of those subjects instead.
    Profile Image for Avdi.
    Author 4 books254 followers
    February 4, 2009
    This is without question one of the essential books on my software development bookshelf. The dirty secret of software is that 80% or more if it is hacking your way through thick tangles of legacy code. This book is your survival guide in that jungle.
    Profile Image for Regis Hattori.
    90 reviews4 followers
    December 2, 2017
    I think the entire book can be resumed as:

    "Legacy code is a codebase without tests. While working in there, you must introduce tests before. If you cannot introduce test because it is hard and/or you have not enough time, use some techniques (listed in the book) that change the code as little as possible or use other techniques that help you write at least some tests."

    I think the majority of techniques are pretty known by developers but we tend not to use them because they sometimes can worsen the production code. The book tries to changes our mind saying that sometimes this trade-off is valid because the tests can help you improve your code later. And I think this is the best contribution of the book.

    Given what was said, I think the book is a good advisor but could be much more succinct.
    Profile Image for Ash Moran.
    79 reviews27 followers
    Edited January 29, 2010
    I've heard this called "genius" and it is. Legacy code is defined as untested code. Changing it involves various strategies to safely and incrementally get tests in place. The "seam" model of thinking, where you identify points you can influence behaviour without changing the code, is extremely powerful. Feathers gives several types of seam, and many techniques for exploiting them.

    The main value of Working Effectively with Legacy Code is low risk ways to deal with untested code. There's a large catalogue of "safe" refactorings. It's important to note, though, that most of the mechanical coding techniques are to work around constraints in statically-typed languages such as C++ and Java. Many, if not most, are unnecessary in eg Ruby. (If you use a dynamic language, the book is a very quick read.) That's possibly my only gripe with the book - that the theoretical core is not cleanly separated from the language-specific issues. On the other hand, it's clear from the tone of the book that one intention is to motivate possibly junior developers to take action on legacy code - so a certain amount of concrete (even laboured) explanation and justification is inevitable.

    Effect Sketches and Feature Sketches are two ideas I hadn't seen before. The former is to reason about how changes may impact other code, and provides a means to identify effective testing points. The latter is a way of identifying separate responsibilities inside large classes.

    I highly recommend this to anyone committed to improving the quality of their code. It's value is _not_ restricted to 30-year-old COBOL systems.
    Profile Image for Michael Koltsov.
    90 reviews44 followers
    December 14, 2020
    The world of software moves with such a pace that this book written in 2004 looks like a relict from the distant past. However, it's still capable enough to teach the old dog new tricks.

    These days most of the software is written with a great help from IDEs that have become much more than just text editors. Every IDE gives you hints on how to optimize your code, most have static analysis tools built-in and most code can be verified even before it gets compiled/interpreted.

    Although, most IDE have capabilities to help you refactor your code, they usually run away in tears when they see hundreds of lines of smelly code in one class not covered by any tests.

    And that's the situation where this book actually shines. It still can teach you a few trick how to keep yourself sane when you're thrown to the snake pit of filthy legacy code. And no matter how dated this book is, I'd still recommend it to anyone who finds himself in a situation where he has do maintain what he doesn't even want to touch.

    My score is 3+/5 Every developer should read this book, but it desperately needs an update
      Profile Image for Vitor Cavalcanti de Albuquerque.
      15 reviews5 followers
      October 21, 2017
      This book is a must for anyone who wants to master software design. It teaches you many techniques and principles to help you on the task of refactoring legacy code (with tests). Even if you are writing a software from scratch, you’ll definitely get to a point where your code will become legacy and difficult to test and to deal with. So I’d definitely keep this book by my side. Besides all that, it kind of helps you see legacy and rotting code from a different perspective: it isn’t just a rotting mess, it’s an opportunity to improve the design of the system, help your teammates, learn software design techniques and keep you motivated. The only cons is that many techniques in this book might be outdated, but you never know when you might need it.
      Profile Image for Aurelian.
      32 reviews
      October 4, 2018
      4.2/5 great techniques on how to effectively work with legacy code.

      Most of my highlights and notes were from " Chapter 17: My Application Has No Structure " e.g. telling the story of the system, a technique that I'll surely use.
      Profile Image for Matt Diephouse.
      90 reviews35 followers
      December 20, 2014
      The tl;dr is that (1) tests make it easier to change and improve code, and (2) it can make sense to “worsen” the code as an intermediate step so you can test and improve the code.

      Noting that “Testing in isolation is an important part of the definition of a unit test”, Feathers gives a long list of ways to break dependencies for testing:

      - Using macros to redefine methods during compilation
      - Substituting in a different behavior for a dependency at link time
      - Passing in objects instead of creating them or accessing a global
      - Moving isolated methods to an abstract superclass that can be subclassed and tested in isolation
      - Accessing objects through getters instead of direct property access
      - …and many more

      He also gives some other suggestions that don’t have to do with dependencies:

      - Using a sensing variable to add tests during a refactoring. The variable can be conditionally set in a method so that you can verify that the correct behavior is achieved.
      - “Extract small pieces first”, as doing so will often help you see the big picture.
      - “Programming is the art of doing one thing at a time”.
      - “One pervasive problem in legacy code bases is that there often aren’t any layers of abstraction”.
      - “Remember that it is okay to extract methods with poor names or poor structure to get tests in place. Safety first. After the tests are in place, you can make the code much cleaner”.
      - “A unit test that takes 1/10th of a second to run is a slow unit test”.
      - “Pay attention to private and protected methods. If a class has many of them, it often indicates that there is another class in the class dying to get out.”

      I found many of those viewpoints to be thought provoking. I also thought this bit about effects was interesting:

      In general, programming gets easier as we narrow effects in a program. We need to know less to understand a piece of code. At the extreme, we end up with functional programming in languages such as Scheme and Haskell. Programs can actually be very easy to understand in those languages, but those languages aren’t in widespread use. Regardless, in OO languages, restricting effects can make testing much easier, and there aren’t any hurdles to doing it.


      Many of his techniques involve inheritance, which he acknowledges towards the end of the book. So I’m not sure how many I’ll use (I’m more interested in factoring my code to avoid dependencies), though I’ll definitely be thinking about them in the coming months.

      My key takeaway is probably that tests can be more important than the ideal structure (if you can’t have both).
        Displaying 1 - 10 of 246 reviews

        Loading...

        Loading...

        Loading...