Debugging code is an activity that shows a lot of the developer’s personality. It is a process that looks a lot like an investigation, leading to the detection of a failure in an application.
During the investigation, a programmer has to employ some techniques, like information gathering, testing of hypothesis, performing quick instrumentation, etc.
When it comes to learning how a program works, though, there are two main “schools” of debugging: the ones that use print statements, and the ones that use a debugger.
From these two techniques, certainly printing is the most “primitive”, in the sense that it is available in almost any programming environment. As long as there is an output device (which doesn’t need to be the screen/stdio), this technique might work.
The advantage of printing statements is that they are easy to use and program. They also don’t require the program to slow down in order to be used. For example, when working with multithreaded code, printing may be more useful than debugging, because it doesn’t require one or more of the threads to slow down.
Using a debugger, on the other hand, is not a privilege for everyone. There are many platforms these days with very decent debuggers; however there are still lots of environment that don’t have one.
For example, I remember reading somewhere that Linus doesn’t create a debugger for the Linux kernel because he thinks it is not important.
Despite the rant, it is nice to have a good debugger available. Depending on the quality of the debugger, it can do some things that would be possible only on dynamic languages. Things like changing the value of variables during a debug session, or going up in the call stack and repeating some code path, can be very useful when determining where a bug is hidden.
I personally like debuggers, but agree that they should not be an excuse for sloppy thinking. Some programmers believe that is normal to use debuggers to poke at a program, instead of stopping and thinking about what the application is doing.
I like to keep in mind that debugging is a research exercise, and it works much better when we use our head more than the debugger.