Read It Again! - Working Effectively with Legacy Code
The IT industry is often perceived as fast-moving. Anyone who follows the discussions about currently popular web front-end technologies can tell you a thing or two about this.On the other hand, there are books that have lost hardly any of their relevance even after more than 15 years. "Working Effectively with Legacy Code" by Michael Feathers is such a work. It deals with the problems we encounter when we have to adapt hard-to-maintain code in software development.
In this blog post, we'll take a look at the book and why it can be considered a standard reference for dealing with legacy code.
In his book, Feathers defines legacy code simply and straightforwardly as code without tests. Why? Well, only code with tests facilitates changes that can bee made reliably. The tests serve as a safety net, telling us if we have changed the behavior of the software (intentionally or unintentionally) due to our adaptations.
So far, so good. What makes the book so valuable are, in my opinion, three important points:
- Problem-oriented chapters
- A catalog of concrete refactoring techniques
- Illustrative, timeless examples
Much of the book is organized into chapters that address the reader in a problem-oriented way, and this starts with the headings: "I Can't Get This Class into a Test Harness", "I Need to Make A Change. What Methods Should I Test?", "I'm Changing the Same Code All Over the Place". If you find yourself with your refactoring problem right in the headline, you can jump right in and find concrete procedures in the chapter. For illustration, mostly object-oriented languages and techniques (e.g. Java, C++) are used. Although other paradigms such as functional programming are currently on everyone's lips, OO techniques are still the method of choice for structuring complex software systems (change my mind!), and many of the book's approaches can also be used independently of OO.
Martin Fowler's refactorings reloaded: The catalogue
Following these chapters is another part that presents a variety of techniques for breaking code dependencies so that classes can be provided with tests. These techniques, if carefully applied, leave code behavior unchanged, as we would except from code refactorings. They may not yet lead to better code design, but with the help of subsequently added tests, the design of the system can be improved later.
These techniques are introduced with examples and concrete steps needed to apply them. And throughout the remaining chapters, there are references to specific techniques that lend themselves to particular problems.
Approaches that are still relevant today
Reading the book, you are presented with a variety of approaches that help you deal with legacy code. There are, of course, a few places where the state of the art has evolved (dealing with the JUnit library or some language constructs have been modernized, of course), but for the basic topics the book deals with, this is typically not a problem.
And every now and then there are a few techniques that can be used even if you are not directly active in refactoring work. In "Telling the Story of the System," Feathers describes how to determine the rough outline of a large system in a conversation with another person.
When trying to convey the system architecture to someone else in a few sentences, one will have to resort to simplifications to exclude certain exceptions. However, these simplifications, which arise in conversation, can be pointers to possible architectural improvements. Another aspect of such a short description also arises in the case of possible extensions: If, for example, two technical solutions present themselves for a new feature, it may be that, depending on the implementation, the brief description would change somewhat, or a further simplification would have to be formulated so that the description still fits the architecture. This may indicate that one technical solution is at a disadvantage compared to another. In the book, this is illustrated by the example of an extension to the JUnit library.
Michael Feathers has worked out the techniques in the book in many refactoring workshops. A lot of value also comes from the fact that they can be used to help teams with legacy applications to deliver high-quality software on a regular basis again. This is also the goal we strive for in our projects. And for this purpose, it doesn't hurt to take a look at this book even 15 years after its publication.