The right way to approach a legacy code base
On Friday, March 10, I attended the Working Effectively with Legacy Code workshop hosted by Michael Feathers, along with a few other members of my team. We maintain a difficult legacy code base and we were eager to do some practice and learn some useful techniques.
The most important lesson I learned is that any effort to improve an entire code base is almost certainly going to fail. If you have a legacy code base that is hard to maintain and with a limited number of tests, it’s very unlikely that you will make it beautiful, with a very high code coverage. You will never have the time and it is not a wise move.
Stop worrying about making the entire code base clean.
Stop worrying about increasing your code coverage to 100%.
So, what should you do instead?
You need to find a clear reason for change! This is what agile is about and most of the time the reason for change is delivering some value to your customers in the form of bug fixing or new features.
Focus your improvement efforts on the area of the code base that can help you to achieve the project goals.
The Michael Feathers approach
Michael Feathers suggests a very disciplined and safe way for dealing with legacy code:
- Identify what you want to deliver to your customers and why
- Identify the areas of the code that will be affected
- Break dependencies to make the code testable
- Write characterization tests to fully test those areas (and possibly make them clean)
- Make the changes with tests (using TDD where possible)
Main techniques
The main dependency breaking technique is to use a Parameterized Constructor. It basically consists of using constructor injection to provide your tests with the ability to mock a dependency. There are other techniques but this one is the most common and cleaner.
In order to apply the Parameterized Constructor, you need to be confident using the Extract Interface, Extract class and Move methods for refactoring. For .NET developers with experience of R#, this is very easy to do. I don’t think I have used those refactorings often enough, but now I feel a lot more confident in my ability to use them (and I’ve already done it in product code since the workshop).
The focus is on breaking dependencies in order to put a system under test. So don’t be scared to make your software a little bit worse in order to increase testability, and don’t limit yourself to changing production code only for production reasons!
Another very interesting technique I learned is called Revealing Interfaces and it is nicely explained on the Michael Feathers blog. I have already used it on my project at work with success, and it can be very useful to understand how two different modules of your software interact with each other.
Scratch Refactoring is yet another useful technique. I already knew about it but I haven’t used it often enough. It simply consists of taking the code base and brutally changing it for the sake of understanding it. You don’t care if you break it because you know you will throw away the code. Sometimes it can be the only way to understand a very complicated piece of code.
If you haven’t read the Working Effectively with Legacy Code book, you should. You can also check out a book summary I wrote few years ago.
This post was originally published on Andrea Angella’s Software is My Passion blog, where you can read more of his thoughts on productivity, teamwork, craftsmanship, C#, and .NET.