4 Techniques to Tame a Legacy Codebase

In this talk, Nicolas shares practical strategies for working with legacy code—code that’s not necessarily old, but code you’re afraid to change. He redefines legacy code as something that still delivers value but lacks the confidence needed for safe updates. To address this, Nicolas introduces hotspot analysis, a technique that uses Git history to identify risky, frequently changed parts of the codebase. He also explains approval testing (also known as characterization or snapshot testing), which allows you to create quick safety nets around existing code without writing detailed specs. These techniques help teams improve their codebases incrementally and safely—even under pressure.
Share this talk
Transcript
Hi, Epic Web. I hope that you're doing fine. I'm here to talk to you about pain. We're gonna talk about legacy code bases, but hopefully, I'm here to give you some tips that should make the pain easy. First of all, I want to define what I mean when I say Legacity Code.
For most people, Legacity Code is just that vague feeling of pain, all code, hard to maintain. Michael Feathers, the author of Working Effectively with Legasicode, defines Legasicode as code without test. It's a great practical definition. However, in this presentation, I'm gonna use a different one.
For me here, legacy code is profitable code that you're afraid to change, and that's for two reasons. The first one is profitable code. This is something we tend to forget, but, any successful company will have their fair share of legacy code because, it's not a legacy that is painful to maintain if you don't have to maintain it.
I've worked with startups that, you know, crashed and that code is not maintained anymore. Often that code is also paying for your salary, so you have to deal with it. However, you're afraid to change it. I like this definition because it means that it's also subjective.
Maybe it's because it, it is lacking test, but maybe there are other things. More importantly, there are things you can do. Because this is basically you working with that code base. Of course, you do not have documentation, or if you do have, it may be outdated. People who wrote that code are long gone.
There is no test or very few tests, at least not the part you want to touch. And of course, of course, the biggest problem is that you have so short deadlines. You need to fix that bug for the end of the week or the end of the sprint or maybe for yesterday. So what do you do?
Well, my name is Nicholas. I'm a freelance web developer, and I am specialized in working with legacy code bases. I've done my fair share in different languages. I've been blogging since a couple of years now on andersonlegacycode.com.
And today, I want to present you with four techniques that hopefully, you will learn at least one of them, and you can use these to make your life easier. You know? So the first technique I want to talk about is called hotspot analysis. And this is about prioritizing where to start.
And when it comes to prioritization, you may know that when everything is urgent, nothing is. Right? So for personal productivity or for project management, we use different tools for prioritizing stuff, such as the Eisenhower matrix. The Eisenhower matrix, take your tasks and put them into two criterias, the urgency.
Like, should you do that thing by the end of the quarter, the end of the week, by yesterday? Like, how urgent is that? And by importance. Like, if you don't do that thing, what will happen? Will it harm people or make you lose a lot of money or reputation? Or maybe, actually, no one will really notice.
So you sort task with these, and you have four quadrants. Stuff you can just drop or delegate or defer, and the things the few things that you really need to start doing right now. When it comes to code, when people start using tools to assess the quality of their code base, they have something that looks like this.
They look for the importance, like, how bad the code is. And some tools, they even give you a score and an estimation of how long that's gonna take. That d score in eight years was an analysis I made a couple years ago of the React. Js code base.
And, of course, you cannot go and talk to your product manager and say, hey, you know what? If we stop developing for eight years and just clean up the code base, after that, we're gonna ship, like, like, super fast. That's not gonna happen. So what do you do?
Well, most tech leads are stuck with, well, I'm gonna take the most critical issues. But on a large code base, you may have thousands of them. So where do you start? What you're lacking here is the sense of urgency. Sure, it's bad, but how urgent it is?
The good news is you already have this information sitting here, and you're not aware of it. Let's talk about code churn. And once I will say it, it will feel intuitive. Basically, it doesn't matter if that code is in very bad shape if you don't have to touch that code. Right?
So you should focus your energy on the code that you're touching very often. That's the urgency. The frequency of code that you touch is the code churn.
And with this command, if you're using git, you can get the 50 files that you're have been touching the most in the past twelve months, which in general is enough to get a good idea. So we dig into the git log, which is the gold mine I was talking about.
We only care about the names of the files of the past twelve months, and we remove any fancy formatting. Then we can filter out the files that we don't care about. For example, it could be any, a JSON file, markdown, things that are not really relevant, to to maintenance. We sort these file names, and we count them.
And finally, we sort again, this time by the count descending. Right? So the most frequent file change is at the top, and the least frequent is at the bottom. And we take the first fifty, which in general is enough. So here you have the list of files with a score.
You take that, and for the same files, you put the complexity score and you chart that. Like, you can use Google Sheets, Excel, whatever you want. You put that on the table on the chart, and this is what you will see. This is an example from the Docker engine, an analysis I did a couple years ago also.
You can see this on the top left, a very terrible, terrible code. Like, the quality is really bad. So that will pop up when you do a static analysis. However, I would argue we could just forget about it because it's not really something we change very often.
Instead, the daemonunix dot go file is better in terms of quality, but we touch this file all the time.
So I can go and talk to my product manager and show them these charts and tell them, well, you know, if we spend 20% of our budget for this print, refactoring this one file, we will have the best return on investment for effort, and you will start seeing the velocity of the team increase over Sprint.
I use these words on purpose because product managers and nontechnical stakeholders, they will be more easily convinced if you come up with a plan. And hotspot analysis, can you can give you the tools you need, and then you have to speak business language. Alright. That was for the hotspot analysis.
Now, once you have identified the the files you want to refactor, probably they are not tested or you don't you're missing some tests. Automated tests are really helpful because, well, someone will be testing your changes. Either it's you, either you do that manually, or the end user will be testing it.
And it's better if you catch the bugs early. So automated tests are here because they can do the work and repeat that work much faster. But how do you test code that is not tested in the first place? That can be tricky.
The idea when we approach Legasico that we're not really sure about what it does and we want to refactor it is that whatever the system is doing today is the most important thing. More important than what the system should be doing, be careful of that.
Even the product manager may have a a vision of what the system is doing, but you may find a bug and fix it, and then only to realize that other people were depending on that bug. So you need to be careful depending on the system you're working on.
How do we capture what the system is doing when you have no tests, of course? But again, short deadlines, like writing tests on untested code, can take a while. How do we do that? That's where approval testing is coming along. Approval testing is one name.
There are many other names that you may be aware of, like snapshot test, characterization testing, golden master, if you're old enough. These are all the same principle. And if you knew none of these names, approval testing, in my experience, is the one that will give you the most results.
So this is the one I encourage you to be googling for. I could do a whole presentation on just approval testing because there are so many use cases and nuances, but I'm gonna give you the high level recipe so then you can dig more.
The first step in approval testing is to take whatever code you want to test and have it produce some string that you can record in a file. So if your function is returning you a value, that's an easy candidate. In practice, though, it's not that easy. Right?
Instead, what you will need to do is to well, you may have to inject something that will, only be used in tests, but capture interesting variables as the code executes, and then use these to get an output that you can put in in in a file as a string. A test will look like this.
This is an approval test. We don't know what this code is doing. Like, the the description is very vague. Right? And we take a snapshot of it. So we expect that to always behave the same. The first time it runs, it will produce you a new snapshot that you capture.
The second time it runs, it will compare whatever output you have with your snapshot. And if they are different, it should ask you for a validation Because it cannot tell you if the, the change was intentional or not, but it should warn you that there is a problem, something changed.
As we're refactoring code, this is what we want. Once you have that, then you can use test coverage, because test coverage is very good at telling you what is not tested. Right? So use test coverage, you look at your code, and you identify the pieces of code that are not covered by a snapshot test yet.
And then with that information, you try to find new combinations of inputs or variables that will allow you to go through this code. Eventually, all the code you intend to change has been covered. It does not mean it is tested. That's the limitation of test coverage. It only tells you what is not tested.
It cannot tell you if what you're going through is tested. So how do you do that? How do you get more confidence that the code you are testing is actually tested? Introduce mutations, which is an a complicated word to say. Basically, do silly mistakes.
Go in your code, right, and comment out some code, introduce obvious mistake, things that you know will change the behavior, and run your test. If a test is failing at least one, you get more confident that, okay, this will break if the behavior changes. Once you're confident enough, then you can start refactoring.
At the end, if you look up for approval testing, you may have a test that looks like that. So this will be testing the the full combinations of all of these parameters. So this will generate 24 different tests. Yes, there will be overlap. We don't really care. Here, we want to go fast.
It's like throwing a bucket of paint to the wall. It's not clean. It's not great, but it goes fast to get some safety net. Alright. Once you have your safety net, now is the time to refactor the code.
The good news is the time you spent writing this test, you get a better understanding of what the code is doing. So you can start refactoring just what you need to improve implement your feature. And you know that when you're doing that, you haven't introduced a regression. You have safety net to help you, or fix your bug.
Once you're doing that, I encourage you to write some unit tests that will explain what the code is supposed to do. Now that you're understanding it better, it's the best moment to do it. However I mean, you do you. The approval tests, they are temporary because they will, fail whenever the behavior changes.
But the behavior will have to change if you want to implement a new feature or fix a bug. Right? So it is expected. The problem when an approval test fails or a snapshot test fails is that you may not be able to tell whether it's intentional or not. If you can, good job.
You may be able to keep it. If you can't, it's actually better to remove it once you know that the behavior is covered by a unit test. Well, there are much more things you can do with this approval test. You can even do TDD with that, but I put you a link in the slides.
I encourage you to go read if you're curious about it. And for us, let's move to the next method. So so far, we have seen how to prioritize technical debt.
Once you have identified some untested code that you want to refactor, how to quickly build your safety net, approval testing, Now I want to show you a method that is more of a personal productivity approach. How do you deal with unknowns? There's a process for that.
So if you ever been in a stand up saying, well, I'm working on that task and I should be done today, but you keep repeating that after a week or two, then I have something for you. What you're dealing with is a lot of unknowns, and that's normal.
When you're dealing with the Gaseko, you don't know what will happen as you go, so you give your best guess estimate, but we're terrible at this. Okay. When you're sick of doing that and you're really getting into something you're not clear, about what it will take, go with the Mikado method.
The first step is to write down the goal you want to achieve. You can do that on a piece of paper. I'm gonna share with you what tool I use personally. Then you take a countdown timer. Set it to ten minutes.
You may want to go to fifteen minutes, but I would not encourage you to go above fifteen minutes, and you will understand why. So ten minutes it is, and you start working on your task, and you try to achieve it within ten minutes. What happens after ten minutes? Well, the timer rings. Right?
If you were able to complete that task within ten minutes, you probably wouldn't have needed any of that in the first place. So what happens when the timer rings and you didn't complete? Well, this is the process. First, you stop and you think why you couldn't achieve that task within ten minutes.
And write down all of the subtasks that you need to do. Maybe you started doing some of them. The good part is that during this ten minutes, you for you you certainly found out more things that you need to do that could be, a a smaller scope.
So write them down, connect them to your main task, the the the task you were doing, and then you should revert your code. This is important. I'm serious, and this is why you should not put a very long timer. Reverting ten minutes of code is easy. I mean, worst case scenario, ten minutes later, you're back. Right?
It is important to start fresh for the process to work. If you really can't get yourself to remove code or revert the changes, stash them and try to redo them. Finally, you pick a sub goal, so a smaller scope. You put the timer back to ten minutes, and you do it again.
You do that, you keep failing, you keep failing, you keep failing, and eventually, at some point, within two minutes, you will be able to complete your sub sub sub sub task. Right? So what happens when you complete your task, when you're done with that thing? Well, first of all, congrats.
You will feel that dopamine show, I can assure you. You can cross out that task. This is done. It's a good moment to commit. As a side effect, this Mykadu method technique ensures that you keep your commit small and to the point. What is the name of the commit?
Well, the task that you are doing, the sub sub sub sub sub task that you are achieving. Right? So this is done for you. Then you pick another sub task, and you start the timer again and you repeat.
From the outside, when you open your pull request, I can assure you it looks like you were speed running through the feature. Not necessarily that you went super fast, although I can assure you, you probably went, much faster than moving in the blind because it forces you to focus on one thing at a time.
But because every time you die, like you fail to achieve something, you went back in time ten minutes before and you try again with the correct path. It's like the movie Edge of Tomorrow with Tom Cruise, very fun movie. Your colleagues, to them, you will look like that, basically. It looks like that.
The first phase, you fail a lot. Like, you die. You die a lot. You try to achieve that task that you failed within ten minutes, and then you have more and more and more subtasks. The good part is, although you do not have any code committed yet, you are doing work.
And with the Mykadograph that you have here, it is visible. So you can share that to your coworkers. You have something to talk about. Maybe you can even say, well, this part maybe you don't need to do. Maybe you we can reevaluate how much because we we have uncovered all of these, subtasks, dependencies that we had to.
Eventually, you reach a point where you start achieving more than you uncover. This is great. This is where you really feel productive. You basically have mapped out how you should attack the problem, and now you're doing it. You're doing the work. So this is where you can also stop anytime and ship intermediate pull requests.
And eventually, I guarantee you will end up here. This is a very bright spot because it means you're done in ten minutes or less. There is a book about it. It's called the Mykano Method, but really what I just shared with you is enough to get started.
Do not procrastinate saying, well, I need to read the book and then maybe I will apply the technique. No. Do it like I told you, and you will avoid tonal effect. And once you start practicing it and if you have more questions, go read the book. I personally use MindMup.
It's a mind mapping software to do that because it's very convenient to do so. But if you have one, just share it with me, please. Alright. So three different techniques. Let's dig into the last one, coding katas. This is the picture of artists performing in front of an audience.
I don't know you, but whenever I see that, it looks so easy, and I know it's not. It is looking so easy because they have practiced a lot. Right? When you're working with that legacy code of yours and you have a short deadline, to get your stuff done, you're performing on stage. Right? When do you practice?
Well, I'm gonna tell you when. First of all, what do you practice? Coding katas. This is these are playgrounds, sandboxes element that are here for you to practice some skills. There are plenty of katas. I have listed the most interesting ones when you want to improve your skills of working with legacy code.
So the previous techniques I've I've mentioned to you, if you want to try them out, try them on these katas. They are sorted in increasing order of difficulty. If you've never done any of them, start with the gilded rose one. It's a great popular one. You will find plenty of solutions.
The goal is not to finish the kata. It is to try things. Time out yourself, try things for, like an hour, and learn from it. And then if you want a more realistic looking code, go with the expense report cut up. When do you practice? If you're lucky, you can take on your personal time to do that.
I used to do that before I had kids. Now I have two young kids, so I cannot afford doing that anymore. My free time is different. But three things. First, your company has a training budget. Like, if you're at the conference here, it's probably because you are sent and paid for by your company.
Why not next time hire some consultant like me or there are plenty others I can recommend that could coach your team into doing more of how can we work effectively with legacy code? Another thing you can do, second, is, scheduling a one hour meeting every sprint, every two weeks for instance. Right?
It's just another meeting, but I can assure this one will be, effective and enjoyable. It's a one hour meeting, only technical folks, people who want to participate, your colleagues, your team, of developers. What you do during this one hour, you take a code in Kata, you take a technique, and you're like, okay.
Today, we're gonna try approval testing on the gilded rose Kata. Or today, we're gonna try the Mikado method and see how it looks like. Do that every sprint, and I can assure you, you will be much, much more comfortable and productive.
Finally, every year, there is a Global Day of Cod retreat. If you're lucky enough, there may be a community nearby, like a software crafters community, and they are doing Cod retreats sometimes. Otherwise, in November, there is a global day of card retreat, and you can find all the folks that are doing that exercise online. Right.
These are four techniques that you can use hopefully to work with legacy code bases. But I, of course, have much more for you. Right? So first of all, if you want to go further, I have my blog. It's all free. Ton of content. You will find these techniques and much more. Go on andersonlegacycode.com. Find out.
If you're really liking that and you're struggling with legacy code, I wrote an ebook. It's a first aid kit, very practical, with 14 techniques I use the most, including refactoring moves step by step. If you want it, please use that link. You will get a 30% discount. That being said, thank you very much, everyone.
I hope you enjoy your conference. And with this, you can get the slides. And if you want to stay in touch, well, I will see you on Blue Sky.