10 Methods to Help You Debug Legacy Code

A large majority of the work we do in our project is to fix reported bugs. The project code is considered legacy code in the context of test driven development. Not only is the code considered legacy code, in many cases it is poorly written which makes it even more difficult to find the real issue. The code was inherited this way and it is not something that you can simply change even with adequate time and resources the ends may not justify the means to fix it completely. All this makes the issue of finding the solution to existing bugs very difficult. But all is not lost; there is an approach that we can take to make troubleshooting much easier.
I am writing this paper with the goal of outlining a structured approach that we can take to make it easier to debug the legacy code. This paper is comprised of several tips that demonstrate the creativity necessary to find and fix the root cause of a reported bug.
1. Accept that it is not easy
The first thing that you must accept is that debugging legacy code is not easy. In many cases it will not even be feasible to add a unit test to the existing code. Maintaining a large code base that is riddled with poor coding practices, bad naming conventions and violations of SOLID principles is a difficult task. It will never be easy. However it will be easier and far from impossible if you follow these guidelines.
2. Check the Revision Logs
If you are using a source version control system such as Git or Subversion, that is a big advantage for the maintenance of a software project. In our system we use Subversion. Subversion keeps a detailed history of changes from one commit to the next. These commits can help us track down the changes made to a particular file. We can use that change history to determine if any changes caused the existing break.
In our case the revision demonstrated that the code was refactored some months ago and the refactored code actually changed the internal behavior of the code. So by definition the code was not refactored. The proper description was that a refactor attempt was made but the attempt instead broke the existing internal behavior of the code. That alone would have been enough to fix the problem. However, sometimes the code is not clear of its intent. It may not be clear cut as to where the breakage exists. See tip number three too see how to overcome that shortcoming.
3. Reverse Engineer
We, as humans, are visual individuals. The old saying: “A picture is worth a thousand words” holds quite true when analyzing the algorithms and structure of source code. It is much easier to walk a flowchart or a class diagram than it is to read actual source code. Source code can be cluttered with bad variable names, poor structure, too many details. Whereas a flowchart can cut out all the clutter and enable us to see a better picture of the problem.
Unified Modeling Language (UML) diagrams can do the same for showing class relationships. If you ever get stuck on a problem and can’t figure out what the code is doing, pull out a pencil and paper and begin sketching out the diagram. It will be much easier to walk the code using a flowchart or UML diagram than it will be flipping through it in your integrated development environment (IDE). Will it take more time to reverse engineer than to simply walk the code?
It may seem that reverse engineering source code is time consuming but compared to what? If you have to fix the issue and fix it correctly you may have no other alternative. You could potentially be “Playing PiƱata with Your Code” by avoiding the diagrams.
4. Prove the Solution
As a programmer today we have a big advantage over programmers of 20 years ago. We have a very content/solution-rich internet. We can find most solutions to our existing problems using the internet. I am advocate of using the internet to search for hints clues and even entire solutions to problems we encounter. However don’t take the solution you find for granted. Instead test the solution. Prove from all angles that it is the correct solution. Simply finding something and accepting it as the solution to your problem without actually testing it is asking for trouble.
You should prove the solution works, not only if you found the solution on the internet but even you carved it out yourself. You should prove your solution works no matter how you found it.
5. Understand the Language that You Are Dealing With
This should go without saying but you’d be surprised how often this may happen. A programmer wants to find a solution but can’t and it is due to the fact that they don’t understand the use of the language they are working with. This means that there may be certain data structures or programming constructs that you don’t understand. For example you may have never worked with a hash table before and the code uses a hash table. Or you have a lambda expression and you have never worked with lambda expressions.
You should stop at this point and take some tutorials on those particular topics, then come back and look at the code again. Trying to find a solution to a problem that you don’t understand is almost impossible. So take the time to slow down, go teach yourself what you don’t know and come back to the problem once you understand those things you didn’t know.
6. Understand the scope of the variables
Sometimes it is simply not enough to understand the algorithm that you are dealing with. You should also understand the scope of the variables that you are working with. You could be working with variables that are affected by a separate process many files or processes upstream or downstream. You should be aware of other areas that impact your code and other areas that may be impacted by this code.
One way you can check the scope of variables is to look at their access modifiers. Do these variables have function scope (i.e. where they described local to the method or function)? Do these variables have public access outside of the class? Do the variables have protected access where they may be modified by descendant classes? Do the variables have public setters and getters?
It’s important to know the access level in order fully understand the code you are analyzing.
7. Log Values to the Console or to the Log Files
When your debugger is working correctly, you can hover over the variables to see their values. That may be a problem sometimes. If you have issues with hovering to view values, instead use your console or your logs to record the values.
8. Be Courteous when Getting Help from Your Teammates
Your team mates have their own tasks to accomplish. As such be considerate of their time. So if you come to them for help be sure to have specific questions prepared. Don’t simply say “I don’t understand any of this, please help”. Instead ask something like “I’ve analyzed this issue, I have walked the code and it does everything it’s supposed to do in this particular scenario. It gives me x value when it should really be Y value can you help me take a look at that?” In other words be specific about what you don’t understand and have specific questions. Their time is valuable so please be courteous and considerate.
9. Walk the code
If you have an environment where you can walk the code with the debugger please do so. It is a big advantage when you can actually see what the code is doing. It may be doing something totally unexpected. If you can switch between revisions (and you should since you have a version control system in place right?), switch between revisions and watch how the process is different between each revision.
10. Be Creative
I probably missed some other things that could help you solve the problem. You may have other ways to approach the problem. After all, it’s all part of being creative. Being creative goes a long way in solving hard bugs. Don’t be afraid to think outside of the box. Look at the problem from different angles. Put on your private eye hat, get your magnifying glass and kick that bug’s butt with some creativity. That should be fun part to you as a programmer.

Be sure to sign up below to receive updates for this blog right in your email box.


Popular posts from this blog

Simple Example of Using Pipes with C#

Difference Between Adapter and Bridge Design Patterns

IoC Container Vs. Service Locator