Working Effectively with Legacy Code
This article are just my notes from the book. Finally the one that covers my daily job. Books like Refactoring (by Martin Fowler) or Clean Code (by Robert Martin) are all fine, but also a bit academical. I have never professionally encounter a clean code. The code is never enough test-covered these days (here in Prague at least). And this book is the one that deals with non-tested huge code-base or a legacy code for short.
The Mechanics of Change
Of course we all read the Refactoring book, which is almost a bible among the developers. So we know that the change is ubiquitous and we can preserve quality of code over time only by refactoring. However there is a catch - code must be tested. That is why we need to put it under the test first.
Here is algorithm of change for legacy code:
One important question is: What method should I test? An answer to this question could be pretty straightforward if you have to make only a small change. You can check what data it will change and test methods that depend on that data and return something. If you have more classes to change, you should find closest interception point, that you will put under test to make sure you don't break anything.
Another interesting topic are the Characterization tests. These are classic unit tests, but you are not trying to test for right behavior. You are trying to test for an actual behavior at the moment. So when you will make your refactoring in the future, you won't spoil anything (even bugs must stay).
Dependency-Breaking Techniques
Mr. Feathers presents us techniques you can use to break the class dependencies, so you can put your class / method under the test. These techniques work as refactorings (they preserve behaviour), but unlike refactorings they do not depend on existence of tests (they are safe enough, not changing existing code so much). Some of them are:
I think the most important motto of this book is - don't apply the 'code & pray' technique when modifying a legacy code. Code safely by unit testing first.
The Mechanics of Change
Of course we all read the Refactoring book, which is almost a bible among the developers. So we know that the change is ubiquitous and we can preserve quality of code over time only by refactoring. However there is a catch - code must be tested. That is why we need to put it under the test first.
Here is algorithm of change for legacy code:
- Identify change points. Obviously you have to find the place for your changes on your own. Every programmer must have done this a million times, so there is nothing new to you here.
- Find test points. Determine what methods to test. You are trying to protect yourself from introducing bugs.
- Break dependencies. Every class has dependencies on other classes. You must break these to really unit test your class/method. There are many techniques, we'll talk about them later. Just remember - try to minimize your impact on existing code because it is not tested yet.
- Write tests. If you don't know how, I recommend the TDD book (by Kent Beck).
- Make changes and refactor. Well, this is the whole point. This book is trying to enable us to do this. This book recommends TDD, but you are free to use your favourite technique.
One important question is: What method should I test? An answer to this question could be pretty straightforward if you have to make only a small change. You can check what data it will change and test methods that depend on that data and return something. If you have more classes to change, you should find closest interception point, that you will put under test to make sure you don't break anything.
Another interesting topic are the Characterization tests. These are classic unit tests, but you are not trying to test for right behavior. You are trying to test for an actual behavior at the moment. So when you will make your refactoring in the future, you won't spoil anything (even bugs must stay).
Dependency-Breaking Techniques
Mr. Feathers presents us techniques you can use to break the class dependencies, so you can put your class / method under the test. These techniques work as refactorings (they preserve behaviour), but unlike refactorings they do not depend on existence of tests (they are safe enough, not changing existing code so much). Some of them are:
- Extract and Override Call. Sometimes you have only small pieces of code in a class calling other static class. In this scenario, you can extract these calls to a separate method (Extract Method refactoring works fine in many IDEs). Then in test environment, you subclass this class and override this new method with fake behavior.
- Extract Interface. This is the way to make fake implementations. You just extract original interface and then have both production and test class implement it.
- Subclass and Override Method. One of the basic techniques. You create a sub-class in the test environment that overrides some behavior of the original class. Then you inject (with setter) a fake object to break a dependency.
- Adapt parameter. Sometimes you don't need the whole interface of production code in your to-be-tested method. You just need smaller portion of it. So you can extract just what you need and use this new interface as a parameter instead of original production code class.
- Encapsulate Global References. This is one of the refactorings how to deal with global references (static methods). You can encapsulate them into the brand new class. Then you replace their original use of course.
- Expose static method. Suppose you have to change part of the method. You can always extract that part into the static method (which will use only its parameters). Then you can expose (make public) this method and test it.
I think the most important motto of this book is - don't apply the 'code & pray' technique when modifying a legacy code. Code safely by unit testing first.
Comments