Channels ▼


Embedded print Statements != Debugging

We are living in an age of unprecedented language creation. Between the explosion of languages on the JVM and the new native languages, we find ourselves with a happy surfeit of very interesting choices. These options are not just the toy creations of comp-sci undergraduates, but sophisticated products with extensive libraries and active communities. Where they tend to be weak, however, is in tooling. And unfortunately, for many language developers, tooling is a metaphor for the coding front end: They strive to create editor plugins to provide basic syntax assistance. The more important support for debugging is often consigned to the use of printf-like statements to dump trace statements and variables' contents to the console.

More Insights

White Papers

More >>


More >>


More >>

I have always found this substitution of printf for debugging to be a profoundly wrong conflation of two concepts. Yet, because we've all had the experience of using printf or its equivalents to help chase down bugs, we tend to go along with the proposal. Some well-known developers even proclaim their preference for print statements. Consider this statement from "Uncle Bob" Martin in a post decrying the use of debuggers: "The kinds of bugs I have to troubleshoot are easily isolated by my unit tests, and can be quickly found through inspection and a few judiciously placed print statements." (I'm not singling out Martin here. He's certainly not the only person who holds this opinion.)

There are multiple aspects of printf statements that make them very poor substitutes and, in fact, at times dangerous tools.

Location. Martin advises "judiciously placed print statements." Well, if you're in a serious debugging mode, judiciously placing printf is a very difficult thing to do. It implies some strong knowledge of the nature of the cause of the defect you're chasing. My experience is that, frequently, you get the first attempt at printf wrong, and then must start to guess where else to place the statements. Sometimes, it's not even guessing: You need to put them at several upstream points to coarsely locate where a variable unexpectedly changes values. Finally, when you get the right location, you must then add new statements to track down why the variable is changing. It's a mess that brings me to the second point.

Time cost. Every print statement means another compilation and link step. Because of the time this consumes, there is considerable motivation to put in many more statements at each pass, so as to trap the defect wherever it might possibly occur. The result is code literally littered with dump commands.

Complexity. While conceptually nothing is simpler than dumping a variable to the console, in fact, it's no trivial matter. This is particularly true of data structures, especially those containing pointers. Now, the print statement must explain what it's dumping and format it correctly. Really subtle bugs might require multiple lines of extracting and formatting code before the dump statement is useful. Debuggers handle this transparently and allow you to walk lists and arrays with no difficulty.

Heisenberg effect. Print statements can occasionally have unintended consequences on the executing code. This is particularly true in parallel programming because of the problem that print statements will be dumped simultaneously to the console and turn the data into unreadable garbage. To avoid this, some kind of mutual exclusion becomes necessary, which immediately changes the execution pattern and performance profile of the code. (Not to mention the complexity of setting this up.)

Clean up. Congratulations, you found the bug! Now, it's time to clean up your print statements. Unless you were very careful about tracking where you placed them, chances are fair that you'll miss one or two. When it shows up in testing later on, you'll need to dive back into the code for a quick nip and tuck. In practice, however, these statements — especially if they including formatting logic for dumping data structures — aren't actually removed. Rather, they are simply commented out in case they're needed again; thereby leaving unneeded trash strewn throughout the codebase.

In almost every dimension, the dumping of variables to the console is an inferior alternative to using the debugger. It takes just as long, if not longer, to find defects, and the practice inserts detrimental artifacts into the codebase.

When I hear developers say that they're happy debugging with print statements (frequently stated with the inflection of "I'm old school and like doing things with simple tools"), I know that they're not likely to be working on large projects and are definitely not working with parallel code. Likewise, when I hear of a new language that recommends print statements as the way to debug the code, I want to address the suggestion by asking, Why don't you just come out and say it? You don't support debugging yet.

— Andrew Binstock
Editor in Chief
Twitter: platypusguy

Related Reading

Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.




Apologies! I'll follow up with you directly to understand better what your posting guidelines are.



Indeed, you're discussing actor-style parallelism, which indeed solves a lot of the problems involving shared state. As everything in computing, though, it solves this problem but creates others. But, yes, for this situation, it does indeed work.


Reading this reminds of something that I've wondered about for a long time - it seems to me that many synchronization errors in concurrent code could be avoided if we drop the idea of synchronous method calls and instead switch to a message passing model in which every object maintains a message queue, and processes messages as long as the queue is not empty, sending another asynch message back to the original requester when the request is processed. It also solves deadlock problems since the sender never waits.


My own (rather limited and on small projects) experience is that using programming by contract significantly reduces the need for both debuggers and print statements. I'm curious to know if anyone has any experience or something to share regarding large projects?


Chris: That's multiple posts about your product. Please don't do that. The comments pages are not venues for vendor promotion.


"In my experience some IDEs will suspend all of the threads when a breakpoint is hit, so finding raising conditions, timing issues or other multithread problems is almost impossible with interactive debugging."

If you are working in C/C++ you might want to check out TotalView. It has the ability called "asynchronous thread control" which other debuggers (particularly GDB and various derivatives) lack.


Interesting -- I wasn't aware of that reverse debugger. I'll have to look into that.

For those using C/C++ similar functionality is available with


If you are working in C/C++ (rather than Java) there do exist debuggers specifically designed to work with multi-threaded programs. They allow you to synchronize threads, run one or more threads freely, compare data across threads, and set thread width breakpoints.



"Interactive debuggers are great when you can set up your breakpoints and catch some bug in the act, but often, like the printf statements that you refer to, the actual problem happened much earlier and unless your debugger is one that saved all state changes leading up to the breakpoint, you'll need to restart your session over with new breakpoints until you narrow in on the source of the problem. "

There are some debuggers, such as TotalView, that have that precise capability. We refer to it as "reverse debugging" or record and deterministic replay. It captures the program state transitions and can replay them. That means that if you do get to a breakpoint or error condition and want to know how it came to be you can simply click " backwards step" and examine the prior state.

I'd encourage readers to give it a try.


-Chris (product manager for TotalView)


A developer after my own heart! I created an entire "tracepoints" feature in a previous company that I would have loved to share with the world. Single global off/on switch, named points plus groups, trace to buffer(less intrusive), or print,and/or optional side-effects ("crash" here, or call function x at exactly this point in the code) for white-box testing, with a few built-in spots in things like the memory allocate/free functions. An enterprising QA engineer used it to verify that the system would clean up properly (QA allowed no unusual messages on shutdown/reboot!) for every single user-space memory allocation in the startup of the system (took quite a while to run!).


Because there are far too many "programmers" who don't use the debugger even when it's available, and who think that's OK.


A technique I used recently was useful in rapidly locating the root cause of bugs.

This technique applies to event-driven applications; ones in which all relevant behaviour is triggered by incoming "events". An event might be, for example, a message received from a network device.

Design your application so it contains a module M with the properties:

a) M contains as much of the application logic as possible.

b) All event processing in M is deterministic, so the same sequence of events replayed into M will produce the same behaviours.

c) All incoming events sent to M are persisted, e.g. written to a file.

Now you write a tool which can replay the recorded message file into M, and thereby reproduce the same behaviours which occured in M when the recording was made. This is a potent tool for finding the root cause of any bugs in M.

Assuming M is stateful, then the replay must begin with M in the same initial state as it was when the first message to be replayed was recorded. If your application is periodically restarted, then you could take this to be application startup, disk space permitting.

When I used this technique I also recorded outgoing events to the same file. The replay tool could then verify that it was accurately reproducing the same behaviours on replay, by comparing the "recorded" outgoing events with those generated during replay - they should be identical barring things like timestamps etc.

As an aside, in my case M also had log statements embedded in the code, but you could argue these are unnecessary provided replay works reliably.


So there are times when you can't use the debugger. But for all the times that you can, a (good) debugger tells you way more about what's going on and what happened than a print statement can. You can move back up the stack, see what happened before you got to where you are, change the values of parameters to see whether perhaps you are misusing an API call, reset the execution point to try something again...

A lot of coding these days takes part in the context of one or more large frameworks; not all the code being executed is yours and you might not understand all of it. The debugger is your friend, used judiciously it gives you great insight into what's going on.

It can also mislead you, particularly with knotty multithreading issues, or when (as occasionally happens) debugger artifacts distort reality. Debuggers aren't a substitute for thinking, but I really am baffled by anyone who prefers not to use a debugger if they've got one available. Are you so macho you are going to put up a shelf with a brace-and-bit instead of a power drill?


In java, this is why you have aspect-oriented programming, e.g. to allow logging without modifying the original code.


@dleppik554 Very interesting. I'd like to hear more, with an eye towards a possible article. Can you contact me privately to discuss? ( )


@softwarenow "Shouldnt programming languages today just support debug statements directly? That way when you use the debug statements, if you interactively set a switch, logging is off, set it again and it is back on."

I'd be satisfied with more of that, but I'd prefer built-in tracing functionality in all languages. For all the pride we take in the advances in language features today, the lack of a built-in tracing features similar to what COBOL had four decades is a remarkable omission.


This article is complete nonsense. Each debugging tool including printf has its place when it works and when it won't work.

Printf can interfere with timing in a multi-threaded program, but so does the debugger. Both can be almost useless to find the exact cause of a race condition.

I prefer a debugger when available, but sometimes it isn't, or the hardware debugger is slower than slow.


I prefer a good debugger over print statements any day. But, when debugging multithreaded code, I will log debug statements to a file which I can view later. VS2010 allows you to debug tasks separately (another form of threading in the MS arsenal). But, when you don't have that luxury, print to file is best, with a time stamp for relevance.


Shouldnt programming languages today just support debug statements directly? That way when you use the debug statements, if you interactively set a switch, logging is off, set it again and it is back on.

My experience with debuggers is that they are mostly useless except when trying to debug conditions tangential to your code. The one and only time I found a debugger useful was a stress test of an EJB in which the garbage collector could not keep up and subsequently the process received an out of memory error. If I slowed down the test, then the GC could keep up. The solution was to explicitly null out all of the variables explicitly so that the GC didnt have to guess when/what to garbage collect.

Even then, solving this problem took guess work as the debugger couldnt tell me specifically, I had to speculate and my speculation turned out to be correct.

Except for that, I find debuggers next to useless. Logging definitely could not have helped me with that solution.

A better solution is that the compilation of the program forces the compiler to instrument every decision point in the code and render a matrix of information to mathematically identify every variable and decision point and line and etc such that you test against the instrumented code and have perfect results of information dumped. Once you have completed your unit testing, then compile again with instrumentation turned off. Use that code for production. You have basically tested and proven that the code works and completely tested all of the code because you have a matrix and likely a process that graphs the results for you so that you can visually show what you tested.

I used a product like this back in the early 90s, but not since, and really miss it. I have seen nothing else like it since.


Yes, debuggers are very useful for figuring out poorly written (i.e. someone else's) code, especially if it wasn't written with test-driven development. If it _was_ written with TDD, then eyeballing it or a few print statements in the unit tests ought to suffice most of the time.

The problem with debuggers is that, by making it easier to get poorly written code working, they help bad programmers burden the world with more poorly written code. Without debuggers there would be a greater demand for people who do it like Uncle Bob.


I don't think TDD and a debugger work in opposition to one another. A debugger is especially useful to figure out why your unit test or a component test is failing. Logging messages have their place as well, in diagnosing failures in production code.


I have used both debuggers and printf(). Debuggers have the drawback they only work in your lab system, and not in a field unit, unless you have a remote debugger that can attach, and assuming there is firewall access to the unit under test.

A problem with printf()'s is that it is easy to collect millions of lines over a couple of days in tracking a problem that rarely occurs. What is really needed is the ability to issue a snapshot when you detect the event has happened, and be able to dump the previous 10/100/1000 lines of debug output, followed by the next 10/100/1000 lines of debug output. So instead of getting millions of lines of output, you get an intelligent snapshot around the event.

And you need much more than a printf(), something that can dump the variables name & contents, structure, and follow pointers.

I would argue that the code should ship with this type of debug information, as nothing is output until one of the event's triggers the dumping of the log. So often these 'not supposed to happen' events occur randomly in the field, and there is nothing to provide information. It's always a trade off performance vs usability.


Agree. That's the case when debugging any long running process when some sporadic bug happens after several seconds, minutes or hours. The first step will always be being able to reproduce the problem easily and quickly, and tracing is more appropriate than debugging in this case.


Like many of the commenters, I also find that most of my bugs are hard to catch via a debugger. Indeed, stepping through a debugger is often not much more than an enforced code walk-through.

A lot of the bugs I see could be diagnosed without a printf or debugger, if only the stack trace included values for method parameters.

Failing that, there are two techniques I use that give me a lot of traction. First, I have carefully placed throw/catch/rethrow segments in my code in order to emit important variables that aren't in the stack trace (e.g. database keys, so I can see which row contains unexpected data.) I don't simply use print statements; I keep a global java.util.WeakHashMap to associate debugging info with the stack trace. This is a stopgap for not having method parameters in the stack trace.

And second, I frequently write debugging information to a thread-local log that only gets emitted when an exception is thrown. This is a lot more effective than general purpose logging, since I don't have to weed through thousands of well-behaved events to get to the one where things went wrong.

Both of these techniques involve custom code, as I don't know of any open source project that does these, and I haven't gotten to the point of generalizing these to release them as open source.


I've been programming since before interactive debuggers, when debugging was such a pain that we put much more effort into getting the code right in the first place. (Indeed on my first C project we didn't even have a compiler until late in the day, and we made remarkably few errors.) I've observed that one downside of debugging tools that are too convenient is that they engender a kind of mental laziness. They can lead to "let's get the debugger on it, see what's going on" as the first reflex, rather than "let's take a breath, think about what we've observed, make some deductions". The latter, coupled with knowledge of the code, leads you surprisingly often directly (and quickly) to the problem, or at least its immediate vicinity. Knowing that you don't want your code polluted with print statements can act as the brake that makes you stop and think first. To Robert Martin I would say that drugs are occasionally therapeutic. Both kinds of debugging have their place.