Legacy code is associated with negativity
When software developers talk about existing systems, you often hear phrases like these:
“Oh, that’s quite some old code”, followed by a sighing: “that’s awful!”.
In many conversations, I then hear the words “legacy code” being used pejoratively. In many developer circles, the notion of legacy code has become synonymous with “it’s bad code”.
Among German developers, coining a code base as “grown historically” (“historisch gewachsen”) is a euphemism that describes code that has been written under time pressure. It is a way to avoid to say: We had no clue what we were doing. But we had to do it. Fast.
Others, like Nicolas Carlo, instead promote a finely nuanced definition of legacy code:
Legacy Code is valuable code that you’re afraid to change.
The key word here is valuable. By associating legacy code automatically with negativity we often overlook the positive sides of dealing with legacy code.
What is so valuable about legacy code? Let’s find out.
The two sides of software system heritage
Let’s first introduce some terms.
Software system inheritance can be defined as the succession of software systems from the custody of predecessor developers to their successors. I consider this a process of on and off boarding of team members, transfer of knowledge and development duties.
Software heritage, on the other side, are the assets to be passed on from one generation to the next ones. Legacy code and documentation are but visible artefacts from a software system heritage. The rights, ownership and knowledge are usually immaterial aspects of heritage.
As with every heritage, it can be debt or a fortune.
Hence, we should talk about software heritage as the passing on of not only of assets, but potentially also of liabilities.
The software development libraries are full of books dealing with the technical debt part of legacy code: technical debts, bugs and architectural flaws. In the following, I would like to focus on the positive part of software heritage.
Respect your ancestors
We do not ask ourselves whether it is “good”. We ask whether it is “new”.
Never has been this roughly translated quote of Ryszard Kapuściński more true than for our times. When looking at old software systems, we see ancient framework versions, outdated dependencies and an alien programming paradigm of past times.
When taking over a software system, we should not only criticize the alleged mess that has been left behind to us.
First of all, legacy code is often working code. We sometimes do not really understand how it is working. Even worse, we are left alone to understand the question of “why” a system had been implemented in a peculiar way. Nevertheless, we should not be arrogant. Our ancestors were usually neither smarter nor less smart than us. But they invested an enormous amount of time. They already had to reason about the causes of problems, solved them their way. We newbies, instead, have less understanding of an inherited system. We cannot answer these questions with confidence:
- What was the underlying problem or business requirement?
- Why the system has been designed like that?
- Who implemented the system?
- What was their skill level?
- How does the system behave during runtime?
- How was the wider ecosystem of that old software?
Again: Problems, learnings and decisions. All these require time to be solved, made or taken. Time that you did not spend yet, but your ancestors did.
Now as the legacy system solves business problems, it usually also generates revenue. It is an asset. Probably not a very polished, nice looking gold nugget. But it does provide a service and ultimately satisfies customer needs. It pays the bills.
It is not recommended to interfere, without knowing better, in critical parts of such system. The standard advice is to write tests for legacy code. While this is always a good idea, sometimes it may be more pragmatic in doing something else:
- Write log statements and evaluate how the system behaves during runtime.
- Analyze the code history by reading through git history and your ticketing system.
- Read merge request descriptions and debates.
- Read crucial documentation and other additional sources of knowledge.
- Interview team members that spent a lot of time in the company.
- Make writing concise documentation a mandatory task.
- Establish a process that organizes the off boarding of team members. Materialize their knowledge before they disappear.
In sum, get familiar with the mental process that let to the creation of the system. All those measures help you to bridge the knowledge gap that lies between you and the previous generation of developers.
Be a good ancestor
Legacy code – that begins today!
In one of my previous companies we used to say “legacy code” exists the day you write a line of code. Here in three months, we start losing in-depth knowledge of that code that has been produced just very recently.
If you look at your software engineering practice of today, ask yourself some tough questions:
- In which state will I leave behind the codebase?
- Have you documented your knowledge?
- Are the dependencies updated?
- Have you applied coding best practices?
- Is the architecture well-crafted, resilient but open to change?
- Have you polished mission-critical code?
- Have you written tests for this code?
- After all, during your time in the company, have you constructed a palace or a rundown ruin?
You might start looking benevolently at the legacy code base and your predecessors. You may notice: One day, you will become that ancestor yourself. And that day is sooner than later.
If we are honest we probably cannot say “yes” to all of these questions. Yes, we have excuses. We were under time pressure or other resource constraints.
Finally, with the knowledge of becoming soon an ancestor ourselves, maybe we should show some respect for our predecessors when taking over old code 🙃