Legacy software July 29, 2025
Why is it that no one actively writes legacy software? I mean, the world is full of it. I'm sure anyone who has been developing software professionally for a while will agree, but I haven't met anyone who has told me the code they had just written was legacy code. How come there's so much of it then? And what is legacy software, anyway?
Michael Feathers, the author of the book Working Effectively with Legacy Code[1], defines legacy code as follows:
Legacy code is code without tests.
While pithy, I've always found this definition slightly unsatisfying. If I add a single test, is my software then non-legacy? Or does the definition apply to lines of code so that a code base with 90% test coverage contains 10% legacy code? Can software only ever become non-legacy with 100% test coverage? This doesn't feel right.
It would also mean that I could prevent my code from ever becoming legacy if I “just” wrote enough tests. I don't know about you, but I have certainly encountered code in my career that had tests yet I would still classify as “legacy”.
Steve Smith offers another definition on the Modern Software Engineering channel, citing Olly Shaw:
It's any software system with a high cost of business change. And as demand for business change increases, it can't respond quickly and safely. Change can't be sustained, something somewhere will break.[2]
This definition is a lot broader because there may be any number of reasons why code can be hard to change, with missing tests being one of them. I mostly agree. Being hard to change is certainly a property of legacy software. Is every piece of software that is hard to change “legacy”, though? Business change comes in many forms, and no piece of software can be changed equally cheaply along all possible dimensions. If the business pivots 180 degrees, and this requires architectural changes to the software (which are typically hard to do), is this enough to declare the software “legacy”? I think not. I think this definition is too broad.
It is also merely observational, offering no guidance on what to do about it. Is it hard to change? Then it must be “legacy”. Now what? Feathers' definition has the advantage of being actionable. Write tests, and your system becomes less “legacy”.
Neither of the definitions addresses the question of how software can become “legacy“. If someone writes a piece of software now, and someone else considers it “legacy” later, what has happened in the meantime to make it so?
Let's take a look at the story of Mel[3]. I encourage you to read the whole thing – it's a great story. But if you don't, here's the relevant part: It's about a programmer, Mel, who wrote very tricky code for a drum-memory computer. When pressured to make a change he felt was unethical, he inadvertently put a bug in it, but he liked the behaviour so much he refused to change the code afterwards. His successor, tasked with fixing the bug, struggled considerably and eventually gave up. (It reads much better in the original, really, go read it! You'll understand.)
I'm sure anyone reading this story would agree that the code was hard to change. The story doesn't mention it, but I think we can safely assume Mel didn't write any unit tests, either. Was his program legacy software? As long as Mel was in charge of it, I'd say it wasn't. When the need for a business change arose, he was able to make it easily enough, and he would have been able to fix the bug equally cheaply if he had wanted to. It was only after Mel had left and his successor had been put in charge of the program that difficulties appeared. If someone asked you to fix a bug in this program, I bet you'd consider it legacy software, whether there were any tests or not.
But the program itself hasn't changed at all! So its “legaciness” can't really be a property of the code, can it?
Enter Peter Naur. In his classic essay “Programming as Theory Building”[4], Peter Naur presents the view that a computer program is much more than just its code:
In terms of Ryle’s notion of theory, what has to be built by the programmer is a theory of how certain affairs of the world will be handled by, or supported by, a computer program. On the Theory Building View of programming the theory built by the programmers has primacy over such other products as program texts, user documentation, and additional documentation such as specifications.
It is this theory that enables programmers
- to explain the operation of the program to themselves and others,
- to justify the structure of the program,
- and to make business changes in a way that maintains conceptual integrity and thus long-term maintainability.
Since this theory lives primarily in the heads of the programmers, Naur describes the life cycle of a program with the notions of life, death, and revival, where:
The death of a program happens when the programmer team possessing its theory is dissolved. A dead program may continue to be used for execution in a computer and to produce useful results. The actual state of death becomes visible when demands for modifications of the program cannot be intelligently answered.
Does this sound like legacy software to you? It certainly does to me. If someone else later takes over responsibility for the program, they will not be able to respond to the demand for business change quickly and safely (as Shaw put it). The code hasn't changed, but the theory behind the program has been lost. It is not known to the successor, just as Mel's theory was not known to his successor. This is because the code (and its associated documentation) may have been handed over, or down – the word “legacy” is really appropriate here.
I therefore propose this definition[5]:
Legacy software is any software whose theory is not known to those responsible for it.
This definition is consistent with Shaw's in that loss of the theory makes the software hard to change, but it also tells you something about how this comes about and, by extension, what to do about it: keep the theory alive. Naur describes how this can be done:
For a new programmer to come to possess an existing theory of a program it is insufficient that he or she has the opportunity to become familiar with the program text and other documentation. What is required is that the new programmer has the opportunity to work in close contact with the programmers who already possess the theory, so as to be able to become familiar with the place of the program in the wider context of the relevant real world situations and so as to acquire the knowledge of how the program works and how unusual program reactions and program modifications are handled within the program theory. […] The most important educational activity is the student’s doing the relevant things under suitable supervision and guidance. In the case of programming the activity should include discussions of the relation between the program and the relevant aspects and activities of the real world, and of the limits set on the real world matters dealt with by the program.
Naur does not explicitly say how long this period of “supervision and guidance” is supposed to take or how to recognize that the theory has been successfully transferred. My own experience with taking over responsibiltiy for existing software shows that about 2-3 months of active guidance seem to be enough for me to get the gist of it, and I take about 5-6 months to reach a point where my questions to former team members are mostly answered with something like, “Your guess is as good as mine.” This is when I consider my “apprenticeship” over and begin to drive implementation decisions from the theory I have built in my own mind.[6] However, the TTT (time to theory) seems to vary wildly between developers, and getting there at all requires a willingness to grapple with and internalize a theory that is not your own. I have worked with colleagues who had not internalized the key concepts of the software after 2 years on the team. Since they were otherwise quite smart guys I have to conclude they simply didn't want to.
If you find people who are willing to absorb the theory and give them enough time to actually learn from the old programmers, you can keep the theory alive and thus avoid the sofware becoming “legacy“. If you don't, the theory will get lost. This can happen quite suddenly if the team is replaced completely, or slowly over time if the theory erodes due to insufficient absorption by new team members (whatever the cause).
Looking back at Michael Feathers' definition of legacy code as “code without tests”, and applying these thoughts to it, we can seen how tests can help to keep a code base alive. For one, good tests not only describe what the code does in a technical sense (e.g., “skip credit card check if the ‘preAuth’ flag is set”) but also the intent behind that (e.g., “no long-running checks during pre-auth”). They encode part of the theory – in an executable, testable way. And I bet when Michael begins implementing tests for a legacy system, he uses them not only to ensure technically that the behaviour does not change during refactoring, but also to rebuild the theory of the system in his mind.
So why does no one actively write legacy software? Because it's not legacy while they are writing it. The theory of the program is alive in their minds. It only becomes “legacy” when it becomes a legacy, i.e., when the code is passed on to other developers without also passing on its theory. Unfortunately, passing on the theory is not easily done and takes more time and effort than is usually available, which explains why there is so much legacy software around.
Consider the code you are currently writing. It isn't legacy yet. What's your plan for keeping it that way?
Footnotes
Feathers, Michael. Working Effectively with Legacy Code. Pearson. 2004 ↩︎
Smith, Steve [Modern Software Engineering]. (2025, July 16). Learn To LOVE Your Legacy Code. ↩︎
Crawford, Matt. The realest programmer of all. Newsgroup: net.jokes. November 20, 1984. Retrieved July 19, 2025. ↩︎
Naur, Peter. Programming as Theory Building. 1985. Retrieved July 19th, 2025. ↩︎
I first came up with the definition “Legacy software is any software the theory behind which has been lost,” but nowadays it is possible to vibe code software without building a theory in the first place. Following the Theory Building View of programming, this produces instant legacy software, so I had to change the wording a bit to account for this effect. ↩︎
I see a connection there to the concept of Shu Ha Ri, but this post is already long enough. Maybe I will go into it some other time. ↩︎