A debugger is only useful for code that you don’t understand.
If you’re writing code that you yourself don’t understand then a debugger isn’t going to get you much, except maybe if you’re using it as a kind of imitation REPL to learn the language.
If you’re dealing with code that someone else wrote then it can be a bit more useful, since with large codebases it’s not always practical to fully understand everything. However, it’s liable to give you a false sense of understanding, so some humility is warranted.
In fact, I want to make the point more strongly. Operationally reasoning about program behavior by stepping through a specific execution is like doing analysis using a jar and a pile of pebbles. It will work in the simpler cases, but is utterly useless for nontrivial algorithms, including any that involve concurrency or nondeterminism.
> A debugger is only useful for code that you don’t understand.
The problem might not be with the code but with the data the code is operating on. To give you a specific example. Suppose you're rendering something and at a certain point it displays some weird artifacts on the screen. You could go over every draw operation the renderer performs to check if there's a mistake in the code somewhere. Or you could check the thousands of points in the data to see if you can spot the mistake there.
My experience is that in this specific case, it's a lot faster to just debug and trace the program. The debugger can then display the current rendering surface each time you break into it. You'll find out which draw call caused the artifacts and which part of the data it was trying to render.
In some rare cases, you'll discover neither the rendering code nor the data was bad. It looked strange because the data was strange but not invalid or wrong.
The point I'm trying to make is that it's also about speed. You use the tools which gives you the fastest results. Sometimes it's quicker to read the code and reason about it, sometimes it's quicker to write printf's, sometimes it's quicker to use a debugger.
If you’re writing code that you yourself don’t understand then a debugger isn’t going to get you much, except maybe if you’re using it as a kind of imitation REPL to learn the language.
If you’re dealing with code that someone else wrote then it can be a bit more useful, since with large codebases it’s not always practical to fully understand everything. However, it’s liable to give you a false sense of understanding, so some humility is warranted.
In fact, I want to make the point more strongly. Operationally reasoning about program behavior by stepping through a specific execution is like doing analysis using a jar and a pile of pebbles. It will work in the simpler cases, but is utterly useless for nontrivial algorithms, including any that involve concurrency or nondeterminism.