The key points of "Working Effectively with Legacy Code"
21 comments
·September 5, 2025Lovesong
bitwize
> VB.NET
*laughs in VBA*
jaggederest
I got started professionally in ASP/JScript. That's right, ADO through JScript, backend programming in javascript circa 2001.
(If you've never heard of JScript, be thankful. It was microsoft's very-slightly-modified ECMAscript variant)
th0ma5
I've skimmed the source material and it doesn't either except for the general advice of putting it all in some kind of conceptual box and slowly move parts to modern or better managed solutions as you try to reverse engineer it all. The underlying subtext was that it is all probably worse than you think and there isn't much good news... But "containment" in various forms is the direction forward mostly unfortunately.
commandlinefan
Kind of depressing to read because it's so accurate, yet so likely to be ignored.
I remember when Martin Fowler first published the book "Refactoring" - I was so relieved at the time because somebody with some clout that executives might actually listen to had not only identified what was wrong with software but identified a way to fix it and even gave it a name! Boy was I wrong - now you have to be careful how and when you use the term "refactor" because the people who ought to be supportive hear "wasting time".
egypturnash
All the blockquotes are coming in as black on black for me in Safari, Firefox, and Chrome. This appears to be due to a css file called "https://understandlegacycode.com/_astro/ai-support.D3anziw5....". Anyone wanna lay odds on whether that filename is a big tell that much of the text is also written by three autocompletes in a trenchcoat?
KTibow
Vite/Rollup name assets based on one of the included files, which can lead to funny and misleading file names like this one.
allemagne
I read through this book relatively recently and agree with the praise here for the core idea that legacy code is code that is untested. The first few chapters are full of pretty sharp insights that you will nod along to if you've spent a decent amount of time in any large codebase.
However, most of the content in the last half of the book consists of naming and describing what seemed like obvious strategies for refactoring and rewriting code. I would squint at the introduction to a new term, try to parse its definition, look at the code example, and when it clicked I would think "well that just seems like what you would naturally choose to do in that situation, no?" Then the rest of the chapter describing this pattern became redundant.
It didn't occur to me that trying to put the terms themselves to memory would be particularly useful, and so it became a slog to get through all of the content that I'm not sure was worth it. Curious if that was the experience of anyone else.
michaelfeathers
Thanks. After I wrote it a friend said "I think you just gave people permission to do things that they would've felt bad about otherwise." I think he was right, in a way. On the other hand, not everything is obvious to everyone, and it's been 20 years. Regardless of whether people have read the book, the knowledge of these things as grown since then.
iteria
I happen to read this book in my early career. I was mid-level. Beyond struggling with codebases, but hadn't yet developed an intuition with codebases. This book blew my mind then. It gave me whole new ways to thinking. I recommend it all the time to people in early career because how to handle a massive legacy codebase isn't obvious at all when you haven't been doing it for a long time.
ebiester
I think of the second half much more as reference. I can give it to a junior engineer or early mid-level and say "here is an example."
It's harder if they can't read the older examples, but I can google for a more modern example as well. It gives nomenclature and examples.
nuancebydefault
I would argue that legacy code has more chance of going along with test code than new code. Often code is written and tests grow during its maintenance.
Another thought... if I'm tasked with maintaining or adding features to existing code, should I feel responsible for writing tests for the existing codebase?
GuB-42
Tests in legacy code... LOL
From my real life experience with legacy code:
- There is usually a "test" folder, but what they test look nothing like the actual code, they often don't even compile, and if they do, they fail, and if they don't, that's because every failing test is disabled.
- Refactoring? What refactoring? Usually, all you see is an ugly hack, usually noticeable because the style is different from the rest of the code base. The old code is generally commented out if it is in the way, left alone as dead code if it isn't.
- Writing tests yourself? It would require you to put the test framework back in order first, they don't have the budget for that. Something as simple as reformatting the function you are working with is already a luxury.
- Sometimes, some refactoring is done, but that's only to add new bugs as to keep things exciting.
Still, as surprising as it may seem, I actually like working with legacy code. It is a challenge, understanding what it is doing (without reading the documentation of course, as if it exists, it is full of lies) and trying to leave it in a better state than it was before in a reasonable amount of time. It is great at teaching you all the antipatterns too, very useful if you end up in a greenfield project later on and you don't want it to end up like all the legacy code you have worked on before. If people actually use it, it will, but you can at least delay the inevitable.
ZaoLahma
> Another thought... if I'm tasked with maintaining or adding features to existing code, should I feel responsible for writing tests for the existing codebase?
Do you trust yourself enough to not break existing code while maintaining or adding features to it? What would be the consequence of you screwing up? Where would your screw up get noticed? Is it a scary person or group of people who will chase you down to fix it?
I've worked long enough to not trust myself at all anymore, regardless if it's old code or new. Then I evaluate according to above. Usually I land firmly in "yeah, better test it"-territory.
Night_Thastus
>Another thought... if I'm tasked with maintaining or adding features to existing code, should I feel responsible for writing tests for the existing codebase?
Write tests for new additions to the code, when making major overhauls to sections of it, or when a bug is discovered that the old tests did not catch.
Those are the 3 cases I would say you should add a test. #3 is most important, in my experience.
j45
The goal of existing and new functionality is reliability.
What you touch will be on you. So writing a test for your own things, and testing what you are using of the legacy code as you go is a good way to maintain your own coverage in case someone else breaks something in the legacy code, or maybe an update to a library or something goes sideways.
Since you'll be doing a good job of mapping it out below is something elsee. The nice thing is LLMs can help a lot with writing tests.
Reliability can be achieved in a number of ways.
Where I've come across an unknown spreadsheet gone wild, or a code base who's jedi knights have long since vacated, I try to get a sense of putting together the story of how the project started and how it got to where it is today.
If I was cold, I'd first focus on the most critical parts, the most complex, and most unknown potentially to put testing around first.
If refactoring was a goal, for me that means it has to run the same as the old code. One way to do that is see if the existing code can become a service that can be compared to the new service.
LLMs have been really fascinating for me to apply in this regards because I've had to map and tear apart so many processes, systems, integrations and varying interpretations and memories.
AI can even explain what most code is doing no matter how convoluted it is. From there, things like test driven development, or test driven legacy code stabilization is much more doable and beneficial not just for the legacy code, but keeping the AI behaving if it's helping with coding.
chris_wot
Interesting article, but a small issue - I can’t read this properly on my iPhone as it doesn’t fit on the screen!
pmg101
The pull quotes are in black on a black background for me on Brave, made it a bit of a confusing read I have to say!
antonvs
Same on an iPad Air in Safari
j45
Appreciate the intro to the book.
I recently talk therapied some legacy code through Claude Code back to life, but along the way put some frameworks in place for the codebase itself, and overall with how I think about approaching existing implementations, which helped a ton to shed a path forward for modernization.
It ultimately reminded me there's always a ton more to learn and think about, and it's sometimes quickest to learn from others experiences when considering paths forward, so the book is a helpful share, thanks.
All code is eventually legacy code in one way or another, whether it's the code directly, or the infrastructure level. Today's code might be the best one writes today.. looking back in 5-10 years.. one might wonder if best practice was best practice.
umbula
What sort of LLM/Copilot advice file could you write to encapsulate all the good advice here? Would "approach this problem using the ideas from Michael Feather's book Working Effectively With Legacy Code" work?
I understand and I agree with the author points, specially looking to distance yourself from the dependencies these systems are usually entangled with, however:
>But the code examples are in Java and C++ and I do python/JavaScript/ruby/
The problem with real legacy code is that sometimes it's not even in those languages. It's VB.NET, COBOL, AS400, BASIC, FORTRAN...and these may not have a chance to "wrap around your class", or "think about all the ORM code that you use". None! I use none of that!. And I can't even call any tests because I can't extend a non existant class, there's no objects in here!.
The author also says:
>You need feedback. Automated feedback is the best. Thus, this is the first thing you need to do: write the tests.
I don't need automated feedback. I need to untangle this deep business layer that everything is wrapped around in a 40 years old codebase, with practically no documentation and having to modify one of the core systems of the company. Sometimes I can't even trust the feedback the program outputs. In this kind of scenarios where the code is so messy and limited by technical factors, the best approach I have found is to debug. And that's it. Follow the trail, find the "seam", and then start your kingdom in the little space you can. Because if you tell your boss that you are implementing Unit tests in a 40 years old codebase, the first question he is gonna hit you with is "Why?", and the article doesn't give any compelling argument to answer this.