Over the last five years I have had the opportunity to work on a large legacy code application. In doing so I have found a common set of principles to working safely in this type of environment (Often discovered through mistakes!). Books such as Working Effectively with Legacy Code by Michael Feathers are excellent and will give you the techniques to refactor safely, but I have found keeping this list and constantly thinking “Am I violating one of these rules?” has helped me work on legacy code with more safety by showing me early on when I was getting off track. I know there are many tips to working with legacy code, I have just found these to be the most important for me. Good luck and happy legacy coding!
My cheat sheet:
1) Go slowly
2) Make small changes
3) Do one thing at a time
When refactoring, follow the steps one at a time. No matter how tempted you are or how easy it may seem to make two changes at once, don’t. This is often where bugs are created and time is wasted debugging. Even if the interim solution is not ideal, don’t worry, this is not the end product. We are working at cleaning up technical debt one step at a time, not all at once.
You should always read code slower then you would read anything else. We are so used to reading at a certain pace that we try to do the same when working on code, which is far worse when it is code you are seeing for the first time. This is typically where business logic gets overlooked and broken. You may feel like this is moving slower, but it is actually saving you time later and reducing bugs.
If you have duplicate code, focus on moving it all to one place before making changes. I know it is tempting to want to clean it up at the same time but don’t. This is where bugs get introduced and problems are created that take days to fix before the product is releasable again. Don’t combine duplicate code cleanup with another refactoring, remember one step at a time.
Stay on Task by Taking Notes
While working on code when you encounter a smell, it may be tempting to try and address it while in the middle of your task (or in the middle of another refactoring). “Oh this is easy, I will just make this change too”. Avoid this type of behaviour. Instead, keep a list of smells and take a note each time you encounter one. Focus on finishing your task (e.g., Adding business value). Once your task is complete, go back and prioritize your list and deal with the smells in order. Work on the the most important smells if you have time. If you don’t, raise these problems to the team and perhaps create an issue to have them dealt with. The important point is to not go down a rabbit hole of refactoring and cause your deadline to slip, but without completely ignoring the technical debt issue. It is so tempting to want to cleanup every smell you encounter, but this can quickly get out of control.
Hide the mess
If you have a chunk of messy code that is not under test, first try and hide that mess from the rest of the application with a clean, high level, easy to use interface and point the rest of the application to use this interface. Once this interface is in place, write tests. Now you are in a position to cleanup the mess, without having to worry about extracting the mess from the rest of the application at the same time. Also if the mess is hidden away from the application, the rest of the application can be clean, and if know one ever needs to make changes to that messy code, perhaps your time can be put to better use.
Don’t be afraid to take a step back
One of the big advantages in going in small steps and committing often is that when you run into a situation where you make a change and now suddenly the application is broken and you have no idea why, you can take a step back. Don’t be afraid to revert your recent change and start over. This can sometimes be much faster than spending time debugging to figure out why the code is broken. Now if you tried to make a number of changes at the same time and are rarely commiting, this mistake could be expensive. When refactoring, it is fine to go off on a tangent to try out a refactoring, but if the mess starts to unravel into a much bigger, more time consuming refactoring, don’t be afraid to back up and try a different approach. The point is to not create your own road blocks, which sometimes developers do to themselves.
Understand the code before you change it
Any time you are making changes to legacy code, the first step should be reading and understanding the business logic of the code you will be modifying before you make any changes. Often this code is the only documentation available and should be treated with care. If you don’t understand the business logic, why that code is there in the first place, you may end up introducing new bugs into the application, re-introduce bugs that had previously been fixed, and as a result create expensive production issues.