'TL;DR To effectively crack the defenses of legacy codebases, you might need to resort to practices that would be unacceptable otherwise. Hence, Michael Feathers' famous book might not be a good read for everyone.
This book comes highly recommended in programmer circles, but for some reason, it took me a long while to finally get to read it. This colors my impression of it somewhat because it's been many years since I had to deal with the kind of projects and code this book describes.
Have you ever had to deal with real legacy code? What do I mean by real? OK, let's recap. Real legacy code is frequently called "spaghetti code"; another common nickname is the big ball of mud pattern. It's code that lacks sensible structure and - worst of all - lacks tests. Classes may have had some single responsibility a long time ago, but they no longer do. Years of maintenance and under-pressure bug fixes left them leaking abstractions left and right, sending ugly dependency tentacles into each other. Touch one character and the whole house of cards may fall down on you. Unit tests are unheard of.
To be honest, having spent the last few years at Google I've been spoiled. I almost forgot how such projects look. "Luckily" I still remember, however, which makes this book an interesting and poignant read. If the above strikes no chord for you - I suggest you skip it. The book will just seem horrible to you - proposing all kinds of ungodly hacks to break up dependencies in code and make it refactorable and testable. The hacks are a good match to the foe - they're about as awful as the code itself, so young and innocent developers may find themselves (rightfully) horrified. It's only the unhealed scars of old battles that will make this book palatable.
The basic premise of the book is simple, and can be summarized as follows:
- To improve some piece of code, we must be able to refactor it.
- To be able to refactor code, we must have tests that prove our refactoring didn't break anything.
- To have reasonable tests, the code has to be testable; that is, it should be in a form amenable to test harnessing. This most often means breaking implicit dependencies.
... and the author spends about 400 pages on how to achieve that. This book is dense, and it took me a long time to plow through it. I started reading linearly but very soon discovered this approach doesn't work. So I began hopping forward and backward between the main text and the "dependency-breaking techniques" chapter which holds isolated recipes for dealing with specific kinds of dependencies. There's quite a bit of repetition in the book, which makes it even more tedious to read.
The techniques described by the author are as terrible as the code they're up against. Horrible abuses of the preprocessor in C/C++, abuses of inheritance in C++ and Java, and so on. Particularly the latter is quite sobering. If you love OOP, beware - this book may leave you disenchanted, if not full of hate.
To reiterate the conclusion I already presented earlier - get this book if you have to work with old balls of mud; it will be effort well spent. Otherwise, if you're working on one of those new-age continuously integrated codebases with a 2/1 test to code ratio, feel free to skip it.