Legacy Code or: the Fear of the Unknown
When hearing the term legacy code software developers tend to run away screaming. The road to working effectively with legacy code is full of bumps and pitfalls, especially when developing new features. How do you deal with the black sheep of software development? Software Engineer Oliver Zihler offers advice.
We software developers love to work in greenfield projects. We want to setup everything the way we like it, work with the latest, bleeding edge technology, and decide from scratch what our architecture should look like. In a sense, the software we build is our baby. We as craftspeople not only want to deliver a product to a customer, but rather create a beautiful masterpiece that the recipient should hang on his proverbial wall and tell everybody about its perfection. Others should admire its simplicity yet superior design and programming. “This is the Rembrandt of software development, such a marvelous piece of art”, shall they say.
However, to quote a recent super villain: reality is often disappointing. Way too often, we are assigned an existing code base where, from our perspective, lots of other self-proclaimed artists already tried for years to perfect their skills. Well, they tried at least. Rembrandt? I don’t think so… Not even tests provide any more understanding: they are either absent, convoluted, incomplete or take way too long to execute, and their code coverage is far from okay. Eventually, we realise: we’re in the middle of refactoring legacy code. Again.
Legacy Code – every developer's nightmare
Legacy code comes in many flavours. For some, it is just “code not written by ourself”. For others, any code without tests is an instant legacy as our understanding and knowledge about the code quickly fade without proper tests. Overall, legacy code is hard to impossible to change as it typically is a Spaghetti Code mess without any intention revealing abstraction or every adaption introduced may have many unwanted side effects (meaning bugs). In any case, it is a nightmare to work with. It's the code that everybody groans when they need to touch it.
Refactoring legacy code might appear like a tedious and dull task at first sight, but it is kind of rewarding in the end: You learn what not to do as a software developer. Apart from that adaptions are nearly impossible to avoid. Imagine a financial legacy system that used German Marks, French Francs etc. Naturally, with the arrival of the Euro, all these different currencies had to be replaced. The outside world changes the requirements for the system and there is nothing we can do to prevent it. So sooner or later, even the best-shielded legacy system will have to be ripped open and adapted in some kind of way. And bless the soul of the poor engineer that has to accomplish this daring task.
How we master legacy code
What we need for working effectively with legacy code is a set of tools and skills. This set differs from that we use to create new code because many of the refactorings we apply there are not safe to perform on code without tests. So, before we can refactor, we need to test the code. And before we can test the code, we need to make it testable. Things like awkward dependencies (e.g. database accesses) or missing injection points (e.g. objects created with new inside our code) are problems that need to be dealt with to write tests that characterise the current behaviour of the legacy code mess. If we do not write such pin-down tests, there is no fast way to tell if the refactored code behaves the same as it did before.
Only when we master the skills to deal properly with legacy code, reality can be whatever we want. All of a sudden, we can understand this legacy code that used to let our fingernails crawl back the first time we saw it. Intention revealing abstractions emerge where chaos ruled. Changes requested by business won't let us go into overdrive because now, we can quickly find again the place to make our change. Or in the least, we are able to separate the mess from the new code and access it through clearly defined interfaces. Thus, it is of uttermost importance that these skills are known to all software developers. Only then, we may finally not shudder at the first sight of a new code base anymore as we know exactly what we need to do.