Books for software engineers and managers

Working Effectively With Legacy  Code

by Michael C. Feathers

Categories:
Tech Lead,
Star Engineer,
New Engineer

Changes usually cluster within systems, increasing the recurring benefit derived from code  improvements

Because changes cluster in systems, developers experience more benefit from improvements than if changes were more distributed throughout the system.

In other words, we are incentivized to improve the quality of legacy code because we are likely to return to that same spot in the near future.

Most legacy code work involves breaking dependencies to make changes  easier

Much of what makes code “legacy” ties back to untestable dependencies.

In my experience, developers who learn and apply dependency injection experience an epiphany in testability. Their code becomes more durable, delaying the time between original authorship and becoming legacy.

In a well maintained system, finding the place to change may be difficult but the change itself is usually  easy

I’ve experienced this many times in complex codebases. Finding the point of entry and following the code through layers of abstraction may feel difficult. But once you find it, the change itself feels easy to make.

Similarly, adding tests for that change feels easy in a well maintained system.

The hardest problem with legacy code is not having any  tests

Not having tests feels to me like the definition of legacy code, although the author doesn’t go that far.

Without tests, developers tread lightly and fear introducing regressions.

I find that the payoff period for adding tests to code is extremely short, on the order of hours. That is to say the time required to add tests is made up for with increased productivity within a matter of hours.

We perceive a tradeoff between code quality and development speed, but in practice their relationship is synergistic.

Adding tests starts with identifying where change can be  detected

When adding tests to a legacy codebase, I often start with integration tests because only at that level can change be detected.

From there, I can start splitting out code in a way that supports unit tests. Now I have a second layer where change can be detected.

Good unit tests run fast and localize  problems

Test speed and localization go hand-in-hand. A test becomes faster when it runs against a narrower set of code.

In practice, tests suites creep toward slowness and eventually cross a threshold where developers stop adding to the test suite. As engineers, we’re responsible for keeping our test suite fast to ensure the ongoing its ongoing usage.

Unfortunately, reading and trying to understand code looks like you’re not  working

When we look across the office, we expect programmers to be writing code. Not staring at their screen reading.

That kind of social pressure can be unhelpful when working in legacy codebases which are inherently more difficult to understand.

Engineering managers can mitigate this risk by encouraging their engineers to spend time reading and diagramming the code.

Teams unaware of the architecture will experience architecture  degradation

Developers unaware of the system architecture are prone to introducing new patterns that ultimately degrade the existing architecture.

When new patterns emerge without automated testing, we end up with a hodge-podge system.

Programming is the art of doing one thing at a  time

Programmers get excited about refactoring or adding new code and its tempting to start changing a bunch of things. When we change multiple things, it becomes harder to tell what changes are working vs. not.

A better technique is to change one thing at a time, in rapid succession.

Working Effectively With Legacy Code

How strongly do I recommend Working Effectively With Legacy  Code?
7 / 10

Working Effectively With Legacy Code is basically about modifying code to increase testability. This book is very similar to Martin Fowler’s book Refactoring, but with more emphasis on testing than readability or flexibility.

If you feel stuck trying to make changes in a legacy codebase, the final ⅓ of this book is most relevant to you – it’s a collection of refactorings with small practical examples.

My only criticisms of this book are:

  1. It’s a little long. I think the author could have trimmed it down by about 30%.
  2. The code samples feel a little dated at this point.

I skimmed some long code samples in the book because I already understood the concept, but engineers newer to refactoring might need to read more closely.