SPA 2006 – Working with legacy code
This session was run by Michael Feathers (author of the book by the same name). To begin with Michael explained the term legacy; this refers to existing code without thorough test coverage. With this in mind discussion was aimed at C++, JAVA & C# applications that require extension. The goal is to achieve some of the benefits and confidence delivered by Test Driven Development (TDD) when developing on a none "green fields" project. If an application exists and added functionality is requested, should we be adding tests? Michael reasoned that the client did not want just the new functionality but the old too. To achieve this, tests should be added to ensure that the new software has no unwanted side effects.
In order to test areas in isolation we must remove dependencies in the code base. Examples of unwelcome dependencies are; the use of singletons, internal instantiations and concrete dependencies.To expand on these:
- The singleton pattern is, in my opinion, overused. In fact I have heard it described as an anti-pattern. When test driving code singletons become even more problematic. If left in place, the singleton is a barrier to our goal of independence between tests. One member of the group suggested that "singletons are OK so long as they have no state and no behaviour". However, as a dependency exists between the class under test and the singleton class we cannot test without the singleton.
- The term "internal instantiation" refers to the creation of one class from another. This structure results in a direct dependency from one to the other. The impact again is the inability to test a class in isolation.
- Concrete dependency describes the previous two cases and many more. Any direct dependency on a class (rather than interface) is cause for concern.
The most direct approach to all of these is to extract an interface and parameterise methods or constructors with reference to that interface. However, some will argue that the code is not quite perfect.
Michael reminded us of a Voltaire quote: "Best is the enemy of good". We need to make many small improving steps. Do not look for perfection in one step.
The following algorithm was suggested as a route to making a change
- Identify change point
- Identify test points
- Break dependencies
- Write tests
- Refactor or change.
When writing tests we often wish to replace real behaviour with test behaviour. The example given was to stub out code that sends an e-mail. In this situation we wish to change the behaviour of a peace of code without touching it. The approach described by Michael was to identify seams. Examples of seams are:
- Pre-processor – for example, remove the use of the doIt method with
#define doIt doSomethingElse. - Linker – this technique in C / C++ is called link time polymorphism. All we need to do is link in an alternative library implementing the same classes or functions. In JAVA we can consider the class loader as operating at this seam.
- Polymorphism – in OO languages our most effective seam is polymorphism, this is the run time dispatching of calls due to inheritance.
The next technique to be presented was, when trying to stub out utility classes introduce an instance delegator. This involves gradually turning a utility class into a real class. Finally Michael suggested an added benefit to reducing dependencies, to often teams have policy of going for a brew while waiting for a build. Reducing dependencies allows localised development with minimal compile time.


I suspect one should mention Michael’s book, which covers all of this stuff in rather more detail:
Working effectively with Legacy Code.