Also available in Czech, kindly translated by Aleš Roubíček
Writing Legacy Code* is a distributed activity.
*In Working Effectively with Legacy Code Michael Feathers defines Legacy Code as “Code with no tests“, which reflects the perspective of legacy code being difficult to work with. I’ll stick to this definition.
Oh, no! Bad code again
You have been assigned a new task.
Your mission: to add a simple feature to a Corporate project. You know almost nothing about it, but the feature request sounds feasible. You can easily accomplish the task.
This is what you think. Until you open the code base.
A mess. An ugly, irritating, awful work. Your monitor displays a disappointing mix of spaghetti and lasagna code.
It seems that someone intentionally performed the complete collection of all the known anti-patterns.
You rage at him. You loathe the guy who made this. This huge ball of mud doesn’t allow you to work properly. You wanted to code as a Software Craftsman, and adhere to all your beloved patterns: you promised yourself to produce code with high cohesion and low coupling and blah blah blah.
But you simply can’t.
How could you? The bad guy used globals everywhere, he wrote most of the methods as static, he created a lot of duplication, and he made no tests at all.
Oh, and look here! He’s using a magic number
int HRW_UND = 12;
What does the damn 12 mean? And what’s HRW_UND? May he burn in hell, the bastard.
Yes, because you have no time. The deadline is close. And you’ve been asked for an additional feature, not for the refactoring of the whole system.
This is not your mission, is it?
Legacy Code always wins over Good Code
Ok, the bad ass who wrote this jungle won: you will add your feature without the strict, inflexible rigor you promised. After all, you didn’t produce this mess, it’s not your fault and you have to accomplish your task.
So, you are forced to under-perform.
But it’s not your fault.
You are justified, since it’s the Legacy Code Writer’s fault.
Here, you would like to add a dependency using Dependency Injection, but it would be such an effort to change the whole thing. It’s all static, no interfaces, too much inheritance and a lot of new statements. You have no time, and the ugly code base doesn’t help. Fuck the Inversion of Control.
You better add a static method to the Service Locator. You hate Service Locators, but the Legacy Code Writer, the bastard, wrote one, and removing it is too much expensive.
Look here! He mixed two responsibilities in the same method, producing a monster. You need just a single behavior of this method, but the code is tangled, and it’s pretty hard to separate the two behaviors, especially because the class lacks a test harness.
You end up copy-pasting a selection of lines.
And look at this method! 50 lines of code, with 18 if-thens. Why hasn’t he used a Strategy Pattern? Refactoring the whole thing or adding the 19th if?
The choice comes easy. Legacy Code Writer, may you burn in hell!
Finally, you need a new configuration parameter. You would introduce an injected property. The bad guy used HRW_UND = 12. You have to bleed like hell to refactor his work.
So you end up adding HRW_UND = 98
It’s all his fault.
You produced legacy code because of the Legacy Code Writer.
No one writes legacy code from scratch
Chances are it all sounds familiar to you.
Now, please, reassess your firm belief: are you really sure that the bad Legacy Code Writer does really exist?
Are you sure that every time you find legacy code there must be a mythical programmer who wrote from scratch the whole thing?
You know what I believe? That this is a very pleasant and comfortable cop-out.
This mythical programmer does not exist at all.
There isn’t a single person who is responsible for the whole mess. The bad programmer, the Legacy Code Writer, is a fictional character we invented to justify our little, continuous, sins.
I came to the conclusion that writing Legacy Code is a distributed activity.
Each of us contribute, eventually not intentionally, to this crowd, incessant, slow process.
Don’t waste your time trying to seek him: we are that programmer.
Legacy Code is like dust: as you rest, it settles, no matter if you aren’t looking at it
Don’t you believe?
Try to see this from another point of view: code does not naturally tend to get, perform and look better just because a bunch of programmers are adding features to it.
This is a natural, simple law, to me: live code tends to get worse unless an explicit, intentional force is applied against the natural tendency to entropy increase1.
Legacy code is what you naturally obtain if you don’t constantly apply a zealous, exceptional and tireless rigor to code maintenance.
Two bad choices don’t compensate: they accumulate
The first version of the project was probably well designed. It came easy: the programmer had a white paper in front of him.
Gian Marco Gherardi loves to say:
Everyone can write good code from scratch, and sustain the pace the first 3 months.
Only professional programmers can maintain it for the next years
So, don’t criticize the first programmer. He probably made a good job.
But as soon as you rest for a while, you’ll be producing a little, tiny particle of Legacy Code.
The problem with particles of Legacy Code is that they are like gems of corals: they tend to attract other bad code, and they soon become as hard as hell to remove.
Introduce a temporary variable, and be sure that the next programmer will feel justified to attach some code to it.
Copy/Paste only 5 lines of code, just a tiny duplication, and expect that the future yourself will modify one of the two copies, forking the code and creating the next headache for several people who will struggle in order to refactor it. Probably, resigning.
Miss a unit test, and be sure that the next team will be thinking
The previous programmer felt that covering this with tests was too much expensive. Well, now it’s surely even more expensive than before. Let’s continue his school.
You made the rot start. This is what is called the Broken Windows Theory.
The moment you add a field to a class, the decision to mark it private or public may seem silly and unimportant, especially when you have such a great pressure from your business stakeholders. The programmer who stops and spend minutes thinking about it, could seem a fool to someone.
Should you find him fool too, you will probably get the less expensive decision.
Only 2 months later, some one will realize how hard is to decouple your class from the rest of the system, because your public field invited everyone to use it. The rollback then could be too much expensive: you will have contributed to the birth of Legacy Code.
Every time you don’t code as a perfectionist, you are condemning the future programmer (and the future yourself) to underperform.
Uberto Barbini once wrote:
Every Brown Fields used to be a Green Field once, and every Green Field will become a Brown Field sooner or later. It’s the order of nature.
Unless an exceptional, intelligent, unnatural, force is constantly applied against this tendency.
So, what to do?
Working on software (even on a single, trivial line of code) you are always in front of a dichotomy:
- apply the Boy Scout Rule: always leave the base camp better than you found it;
- or just add the little behavior you are supposed to deliver, eventually making the code just a little more tangled. Just a little little bit.
I realized that the second behavior is the most common one. Admit it.
Being exceptionally strict, every single minute of your career, is too much stressful.
Nowadays, when I face a piece of Legacy Code, I don’t swear against the mythical Legacy Code Writer, since I’m starting to understand that the culprit it’s me, it’s you, it’s almost every one I know.
I started thinking that my mission is always to refactor systems (even when my boss just asks me to add a feature), and that I should be refactoring deeply, at the point that adding the requested feature should be a natural consequence, almost a collateral effect.
A clue that our mission is more “refactoring existing code” than “adding new features” is that it’s much more likely that projects fail because they become unmaintainable, rather than for a lack of features. Don’t you agree?
Since I don’t want to be confused with the legendary Legacy Code Writer, and offer a cop-out to the future Legacy Code Writers, I promised myself to always be a perfectionist (with deadline) or, at least, to try to sense the technical debt I am leaving behind me.
Each little contribution may make a difference between a tangled system and a sustainable future.
After some weeks I wrote this post I discovered that my sentence
Live code tends to get worse unless an explicit, intentional force is applied against the natural tendency to entropy increase
is astonishingly equivalent to the Lehman’s Law:
As an evolving program is continually changed, its complexity, reflecting deteriorating structure, increases unless work is done to maintain or reduce it. (Meir Manny Lehman, 1980)
I swear I didn’t know it the before I wrote this article. But I’m very happy that my arguments are supported by such a great authority in Computer Science.
Many thanks to Eleonora Gorini.
We once worked on a project together, and we must have shared the same mindset, since one month after I quit the Company she wrote me a email telling me how rewarding — rather than demotivating — is refactoring her current, unbelievably messy project.
This what makes the difference between a common programmer and a pro.
Thanks for the unexpected present, Eleonora.