Which is more effective: Debuggers vs. Print Statements

Posted June 12th, 2006
Categories: Technical, Programming


Every application out there is going to need some debugging. The question that comes to mind is what is the most effective approach to debugging the application you’re working on? There’s a number of ways to debug applications, and everyone has their own war stories on how they’ve figured out some of the most impossible things after digging in the trenches of code.

What I already know is what works for me. I’d like to know what works for everyone else in the same situations. I’ll explain how I’ve seen debugging in the past, and how I think the different methods should be applied.

Common debugging approaches

Here are some of the popular debugging approaches I’ve used and seen used for the most part in every environment I’ve worked in:

  • Enable the debugger in your IDE and follow the application’s progress within the code – this particular approach allows you to see the actual flow of the application while it’s being run. You can take a look at the values of all your variables every step through the way and you can fast forward to any point in the code you want to see.
  • Insert print statements into the code – this approach allows you to see what code the application is running through by watching the print output. You can see the flow, and print the values of variables onto the screen as they’re being used.
  • Modify code and monitor data sources – this one’s a bit more time consuming so I won’t be highlighting it much in this post. Sometimes you simply need to see what your changes did by running the application and watching for changes in the database or in the files you’re working with.

When debuggers are better

I find that debuggers are far more effective when you simply don’t know where something unexpected is happening. If you have no idea why your website isn’t doing any validation when people enter incorrect information, rather than hit and miss with other debugging methods…using a debugger allows you to step through the application one area at a time until you see where it does something wrong.

The convenience of being able to step through one line at a time on command or jumping to the next breakpoint in the IDE is a feature of debuggers that saves a lot of time. Having to trace through 1000s of print statement outputs in a console window is far less effective than having the program stop right where you want it to so you can scan all the variable values for anything of significance.

In a complex application, debuggers allow you to more quickly pinpoint the area in the application that’s causing issues while stepping through the code. This is especially noticeable in the environments where you can back up or “rewind” to another point in the app to double check workflow.

When print statements are better

Print statements are something you can insert into the application without setting up any additional software or environment settings. You can go straight to the source, insert your statements, and run the program to see the immediate output results.

On smaller-scoped issues where you’ve already a good idea where the problem lies, sometimes it’s easier to just go slip in a few print statements to see what’s going on. This tends to be easier in environments where there’s many integrated systems to work through and you simply want to test out one little piece.

If you have absolutely no experience using a debugger, tossing in print statements is a very low-learning-curve method to get inside your code to see what’s going on while you run it. Anyone can do this without learning about special plug-ins or programs.

Print statements also seem to run much faster than a debugger. Debuggers seem to slow things down in multi-threaded environments much more-so than print statements.

Conclusion

In general, I tend to use print statements while I’m debugging. I’ve been doing it for years, and I can’t think of many problems I can’t find with carefully placed printouts. However, overall I think that debuggers are more effective if you take the time to set it up in your environment. I think the initial time it takes to set up a debugger for your application will make itself up after a few run-throughs.

I’ve kept this article pretty general in hopes of being able to relate to most people out there without alienating any language-specific environments or IDEs of choice. What debugging approaches do you use? What preference do you have when figuring out an anomaly in your program? I’d be interested to see how others “in the trenches” deal with fixing their applications.

Tags: , , ,


del.icio.us  Digg  Reddit

Related Posts:


Explore posts in the same categories: Technical, Programming

5 Comments on “Which is more effective: Debuggers vs. Print Statements”

  1. Curt Says:

    As you mentioned, it is definitely situational and you should use the right tool for the right job.
    This is getting a little more language specific, but consider debugging multi-threaded applications. If there are issues with blocking/synchronization you may not be able to reproduce the problem using a debugger as it will disrupt the timing between the threads. In that situation I make sure that I pepper my code with print statements - always making sure I add ‘too many’. Nothing is worse than having to add more statements, recompile, re-deploy, re-run the tests.

    Curt

  2. Retrospector Says:

    Something to consider in a multi-threaded environment is being able to figure which thread is doing what when reading through the print statements. Web apps, for example, it’s helpful to prefix the statements with session id or something so you can follow step at a time for each thread.

    I’m right there with you on just dumping out plenty of prints while coding though, even if the only thing to distinguish them is a number. Something I do quite a bit in my code is this:

    int iDebugLine = 0;

    System.out.println(”Debug: ClassName::MethodName() - ” + iDebugLine++);

    This way I can copy/paste the thing as many times through a complex method as I want to without much trouble.

  3. Jonathan Allen Says:

    I think sprinkling print statements throughout your code is no better than guessing.

    Any decent IDE should have an interactive debugger that can run the application directly in debug mode. The good ones can also attach to an existing process, automatically pulling in the correct debug symbols and source code listings. Since I know how to use my debugger, it takes me less time to run it than it does to add print statements and recompile.

    I also have the advantage in that once I find the bad value, I can actually examine it. I can see exactly how it flows through the program. This lends me a level of true understanding that no one can get just looking at static code listings and printouts.

    With a really good IDE, I can even correct and rerun that portion of the code. This is crucial in larger applications where it can take 20+ minutes just to get back to the same place.

    As for multi-threaded applications, this is where a debugger can really help. Once I think I know the cause of the race condition, I can attempt to recreate by carefully controlling the threads. Since race conditions are intermittent by definition, this is often the only way to reliably prove a theory.

    Jonathan Allen

    Some people program with rocks. Others use a hammer. I prefer a contractors’-grade pneumatic nail gun with a belt-fed magazine. Have fun “becoming one with the metal”, I’ve got real work to do.

  4. Valentin Says:

    I do prefer print statements in a very generic way, but actually I never wrote print statements in production code as it looks bad and might not be very efficient. What I use is logging, which is pretty much like print but can be fancier.

    Logging is IMHO, a must for any program, unless performance suffers. I think most of the software can have logging with pretty much no performance penalty, especially if logging is done correctly. I personally prefer using log4(cxx, j, ..) libraries for logging since the performance is pretty good and recently they’ve became a standard for logging.
    I can structure logging for different levels so when my program runs in production little or no logging is produced but when I want to debug an issue I can just ask for more logging just by changing an env variable. For this I don’t need to take the library out of production environment or to change the library.

    As for debuggers, I still use them, although I’ve never used an IDE base one, and I think during the development they are nice, and might save some time in identifying simple problems. There are situation when using debuggers is very hard and could take longer to find a bug (e.g. a Fibonacci like algorithm that crashes when it riches 8921 iteration).

    Another problem with debuggers and C/C++ is that debuggers are platform/compiler dependent where as logging is pretty much independent of the platform or compiler.

    In conclusion I think any software developer should know how to use any of the two methods to find bugs, but I would weight the print/logging method more than knowing how to use a debugger for a certain compiler/language/platform, as more benefit can be drawn from logging.

  5. Kim Greenlee Says:

    I agree with the idea of debugger first, but many multithreaded problems are timing related. This means that you may only see the problem after a very long execution time on a multiprocessor box or the problem only manifests in the release build. Under these circumstances the debugger is not an option. While I’ve used print statements (to the screen) for quick and dirty debugging in both multithreaded and distributed applications, I generally like to build a logging system into the application and then use the generated information as a starting point to identify the problem. The information in the log file is only a clue to point the developer to the areas of interest in the application. Once I’ve identified the areas of interest I go back into the debugger and step through that code with an eye to potential threading mistakes.

    I have also used debugging tricks such as inserting an “int 3” either in my code or into the actual assembler code of loaded 3rd party DLLs. (This is platform specific). Something I only do when all other methods don’t work.

    And keep in mind that the applications running inside of a debugger inherit the debugger’s application space so that some types of bugs, such as bugs related to system resource consumption, can be masked by the debugger. (Again this may be platform specific) With these types of problems it’s necessary to fall back on log files and external system monitoring tools.

Comment: