There may come a time as a programmer that you have to support a crucial existing system and while the task is daunting, there are still many actions you can take to make that process a lot easier. When you have to work with a deep expansive legacy system it’s easy to get lost in a lot of the details. Here are six tips that definitely would’ve saved me, and hopefully you, some time when getting introduced to a legacy system regardless of web or desktop.
1. Ask as Many Questions as Possible
One of the fastest ways of getting familiarized with legacy code is to simply ask a lot of questions from the previous developer if that is possible. Starting off, it might not be possible to know of all the specific questions someone may have about the code. Ideally, you should try to ask questions about the overall structure and algorithm of the program. This helps create a more defined mental map and allows you to quickly identify places to edit/change in the event you need to implement new features.
It is also valuable to ask about previous places the developer had difficulty in or code they believe is not as robust. There is no reason to try and resolve an issue twice if the previous developer already identified the problem. Simply keeping this information in the back of your mind whenever you write a new code that interacts with that section can help to debug smoother. However, not everyone can have the luxury of having the previous developer available, in which case you can try your luck with the client or userbase actively using the code for any history they may remember, in regards to bugs or already implemented fixes.
If either way is not possible, there are still many things you can do to make working with legacy code a lot less painful, which I will cover in the following tips.
2. Document Call Hierarchy
When working with a large spanning piece of code, it can get difficult tracing down how a function or procedure works, especially when you have to make edits that may have far extending effects that you need to account for. You can create a simple call hierarchy by noting down the specific function you call, and all the subsequent calls that function makes in sequential order.
For instance, if you have a function Run which calls function GetItems and later on SortItems, you can keep track of the process and all the necessary places you have to change the code to maintain functionality. This is especially true if there are further calls in the subfunctions such as GetItems calling its own subfunction of LoadFile. This process of documenting the hierarchy should be kept relatively simple and easy to read, something as simple as the name of the function should work.
A key point is that you should not go in-depth as to what each function does, especially if you are looking at the code for the first time. The main reason you want to avoid that is that by documenting excessively, you might develop biases or assumptions that are initially inaccurate.
After working with the legacy code, you’ll discover nuances and small details that you may have overlooked in the first pass through that change how the process runs, like a flag being set on a variable or specific timings that may have been missed.
Keeping the documentation of hierarchy simple will allow you to identify and understand the structure, but allow you to avoid common pitfalls and red herrings that comes from not being unacquainted with the code. As you get familiarized with the code, you can develop more diagrams and discover how your new or edited code interacts with the rest of the system.
3. Double Check Your Assumptions
A subsequent tip that was briefly touched on is that you may develop assumptions after going through the first pass of the code. When bugs occur, one of the first things you should immediately look at when you have reached a dead-end is to re-check your initial assumptions. In most cases, a lot of bugs that come from editing legacy code comes from a misunderstanding over what you believe something does, and what it actually does. After reviewing changes that you implemented and making sure it works, the next step should always be to double-check assumptions that you previously made.
For example, this can occur in conditional statements, where you believe the code would go into a specific conditional branch but instead goes somewhere else entirely. Another place this can occur is in function calls that have side effects that might have been missed, setting flags in other places of the code.
4. Testing, Testing, Testing
This might be obvious, but after you make any changes, extensive testing should be done to guarantee not only that the new function works, but that everything else behaves as it did before the changes. Setting up unit testing and functional testing ahead of time can go a long way in saving time and effort by having a set of pre-defined test suites to verify that everything is working in tip-top shape.
In cases where the code is too old and setting up testing cases do not make sense, setting up logging in as many places as possible can be key in debugging potential issues. Debugging and viewing the watch screen is very viable. Having logging sequences in place for each event is just as valuable as an issue could occur and you aren't around to watch it happen, logging will help narrow down exactly where the issue occurred, and some valuable replicable context that can help track down the issue.
For each new addition, there should be at the very least, a new line of logging or monitoring that should stay until it has been verified that the program is running consistently.
5. Start Small and Go Slow
Starting small goes hand in hand with testing everything. Sometimes, a change doesn’t require knowing every single nook and cranny of a large piece of code. In other cases, it requires changing code in many different places that you may not have been identified yet. It can get overwhelming changing code in multiple places and remembering to do it all.
Making sure to go slow and looking at one section of code at a time can reduce a lot of mistakes. This isn’t just applicable to legacy code but can also be applied to most aspects of coding.
6. Document Everything
Lastly, you want to document everything. From the discoveries you’ve made at looking at the legacy code, to the new additions that you’ve added. This can as simple as notes that you can print out, or comments in the code itself. Documentation not only helps you but also helps future developers that may have to work on this software. This information will create stability for yourself when months down the line, you may have to go back and look at your own work. By setting it in a sort of electronic stone, it legitimizes the changes done to the project, beyond just being key points to remember internally.