Debugging is an essential skill for programmers. Our code breaks often, and in many cases we have to debug our code to figure out why it broke. Some people hate debugging while others love it. I am firmly in the “love debugging” camp, I get a huge kick out of solving a bug in my code by figuring out what’s going wrong and why.
When I think about debugging, printf debugging is usually the first thing that comes to mind, though many may not see it as real debugging. In this post I will try to describe what printf debugging is and why I think it is useful.
First, let’s review some common debugging techniques:
- Looking at the output. Some kinds of errors can be found without even looking at the source code. This is common whenever you are working with graphical applications, e.g. in a Graphical User Interface it is often easy to identify a bug and how to fix it when something doesn’t work as intended. Just looking at the program output in non-graphical applications can be useful too. For example, if your program should print a list of items and it prints them in an unexpected order, then the bug is probably that the list is not sorted before being printed.
- Stare at it until you figure out what when went wrong. This is a surprisingly common debugging technique. You can often figure out what is wrong by just reviewing your code again and thinking about how the control flow could lead to the unintended behavior.
- Explain your code to someone else (or a rubber duck). This deubgging method is useful but often overlooked. It is easy to make incorrect assumptions about your code, it’s a mistake every programmer does many many times. When you try to explain your code to someone else something interesting happens: you end up re-evaluating your assumptions and realizing that some of them were wrong, and that can lead to discovering the cause of a bug.
- Printf debugging: inserting print statements in your code to test your assumptions or visualize control flow. We’ll talk more about this soon.
- Using a debugger. This is probably what people associate most with “real” debugging, although it may not be the most common method because it requires additional tools and mental context switching. Debuggers can be hugely powerful though, and there are lots of different ways to use a debugger ranging from simple to advanced: stepping through instructions, setting breakpoints, setting watchpoints, setting conditional breakpoints / watchpoints, attaching to a running program. Knowing how and when to use a debugger is awesome, but debuggers are not the main topic of this post.
Let’s examine printf debugging a bit more. As I said, printf debugging is the practice of inserting print statements in your code. You might not even think of this as a debugging technique because it doesn’t rely on a debugger, but as seen in the list above many debugging techniques don’t require a debugger. The lack of a debugger is in fact a part of why printf debugging is useful. Although many development environments come with a built-in debugger, they usually require a separate workflow to start the debugger. It might not be that difficult, it’s usually quite easy, but it is often just quicker or simpler to insert a few print statements to test your code. I usually find that inserting print statements is less mentally straining than switching to a debugging mode – it means I have to switch the way I think about my code and figure out useful places to add breakpoints.
Here are a few other reasons why I think printf debugging is useful:
- You don’t need extra tools to insert print statements. Just edit the code then build and run the program as usual.
- Print statements can be used to follow the control flow of the code, without stepping through it in a debugger. Using print statements you can easily generate a timeline of of interesting points in the execution. Selected variable values can also be printed in the same high-level timeline if needed.
- Low impact on timing sensitive bugs. Using a debugger interrupts the control flow and can cause timing-sensitive issues to become untraceable. Print statements are less intrusive when debugging concurrent code that depends on a specific thread interleaving to be triggered.
- The printf method usually gives more high-level information than using a lower-level technique tool such as a debugger where you often step through statement-to-statement and see very detailed but often uninteresting information about the control flow and program state.
- A debugger may not be available, then printf debugging might be the best bet to figure out what is actually happening in the code.
Printf debugging does have lots of legitimate uses, however in some cases it’s not the best thing to do. Here are some reasons why printf debugging can be bad in some situations:
- The debug print statements need to be removed after you fixed the issue. This is tedious and error prone. It’s easy to forget to remove a debug print statement.
- It’s boring to insert print statements. When there are many variables you want to know the value of it is probably time to switch to using a debugger.
- Output logging is not always available. If you make apps for a smartphone it might not be easy to get a console to print debug messages on. Many kinds of plugins in different kinds of software don’t have a debug console either.
- Too much output. Printf debugging is sometimes too verbose, particularly when the place you insert the print statements is called very many times before the bug occurs.
I think there is much more that could be discussed about the pros and cons of printf debugging, but the main point is that it’s a simple technique to use and there is no shame in using a less sophisticated tool if it gets the job done. In the end it only matters if you can find the bug in a reasonable time, not which tools you used to find it.
One final thing I want to show is a technique I use when I debug Java code. Sometimes I add the following line to my code in a place where I know that incorrect behaviour is happening:
This will result in a stack trace showing the call stack for the problematic point in the program. This starts to get into territory where it’s better to use a debugger, but if you just want to know the location of one statement in the call stack and are not bothered to inspect local variables etc, then the above line might be a useful hack.