Debugging is one of the most overlooked topics in teaching computer programming. This is despite the fact that it’s one of the most common tasks performed by a developer! Heck, I didn’t even learn about debugging until my very first internship. Afterward, I was blown away by how much easier it was not only to correct my silly little mistakes but also to diagnose problems I otherwise would have had no clue how to solve.
Debugging your code helps to transform your understanding of reported problems into concrete issues. Even though it’s valuable to find issues in your code just by glancing it over, oftentimes problems are subtle and harder to find without being able to step through the code line by line. Working with a debugger freezes the flow of execution so you can peer into program state, enabling you to see what is actually happening instead of what you simply speculate is taking place. This helps us to get rid of what I feel is one of our most heinous enemies in software development: our own incorrect assumptions.
Finding problems in code at times feels like searching for the needle in a haystack. The bigger the haystack, the harder it becomes to find the needle. In much the same way, as our programs grow larger and more complex, it becomes that much harder to diagnose and fix bugs within them. Debugging helps focus our attention on the likely places a bug might be hiding. If the bug is like a needle, the debugger is like a metal detector, assisting our navigation and providing hints to what’s underneath.
Sometimes we may inherit a large project that is completely unfamiliar to us. How are we supposed to speculate about the cause of an issue if we have no idea what’s under the hood? With some codebases, it could take weeks of study to get a good idea of how the application functions. Fortunately, the debugger allows us to easily associate the execution of an application with the code itself. It makes it easy to recreate the bug from the perspective of a user and track the bug from the top level entry point to its place of occurrence. In short, debugging guides us from a place of simple understanding into the details we need in order to solve a problem.
What exactly do Debuggers do?
If you aren’t using an Integrated Development Environment, or IDE, now may be a good time to start. Most IDE’s come with debuggers, and some of them can be very sophisticated. Every debugger will have its own nuances, but they are all aimed at allowing you to do these two things: setting breakpoints and stepping through code.
A breakpoint is a line of code that you can mark as a stopping point in program execution. When you are in debugging mode and the program reaches a breakpoint, the program will halt. At this point, you can view the state of your program by checking the values of local and global variables, but what’s really cool is that you can run the program line by line at your discretion. This is also known as “stepping through” your code. There are a few ways to do this.
Step Over – This executes the line of code that comes next in the flow of the program. Usually, a debugger will point to the next line of a program with an arrow-like symbol.
Step Into – If your breakpoint pauses on a line that calls a function or method, you can tell the program to “step into” it. Stepping into a function effectively puts you in the context of that function. This means you can step through each line of the function instead of stepping over the entire function at once.
Step Out – This is pretty much the opposite of stepping in, as the name implies. Let’s say that a function named “A” called another function named “B” and you are currently debugging through lines of code in function B. Stepping out effectively executes the remaining lines of code in function B and puts you back in the context of function A, halting the program again just after the completion of function B.
Stepping through code and setting breakpoints can be a lot like finding information in an encyclopedia. Searching through the index gives us a starting point or “break point” from which we can read or “step through” the text until we find the information we need.
Debugging through code allows us to study the flow of a program much like a musician learns to play through a few pages of sheet music. It helps us to understand a program not just as an abstraction in the form of code, but as an actualization in the form of a working process. In this way it provides a sort of sanity check, showing us what is actually happening as opposed to what we think should be happening. And of course, debugging helps us to find and fix bugs.
The ability to use targeted searching is a skill possessed by any experienced diagnostician, let alone an experienced debugger. Those who excel are adept at balancing research with assumptions. We seek to understand, but learning takes time. We seek to build upon what we know, but we shouldn’t always count on our assumptions being true.
If you haven’t experimented with a debugger before, put aside some time to check one out. You’ll be glad that you did, trust me!