What are the big guns? with a debugger, I can stick a breakpoint and look at the entire state of everything. Given we're talking about Python, in pycharm [0] you can even execute your print statements in the debugger if you so wish. If you get the location wrong, or want to see what's going on elsewhere you can just continue execution and use another breakpoint.
This is even more important if you have a long compile/deploy cycle (I work in games, and rebuilding and deploying to a console can be a >10 minute iteration time)
[0] https://www.jetbrains.com/help/pycharm/part-1-debugging-pyth...
In these cases prints work well as a less intrusive way to get a rough idea of what is going on.
> You might not even know which wheel to jam the debugger stick into, if the behaviour is complex.
If you don't know where to put a breakpoint, how do you know where to put a print statement?
Also for multithreaded code, stopping one thread dead for long enough for a human to investigate it can inadvertently resolve all sorts of race conditions.
No one is saying breakpoints are useless, sometimes printing is 'cheaper' in time and effort in order to locate the region code of code in which using breakpoints is cheaper.
[0] https://docs.microsoft.com/en-us/visualstudio/debugger/using...
I don't think it is, at all. The cost of using print is re-running your applciation with a code change, whereas the cost of a breakpoint is re-running your application with a breakpoint. Clicking in a gutter in an editor, pressing a keyboard shortcut, or typing "b <line number>" into your debugger is no more time or effort than adding a print statement, and re-running your program.
If you have enough loops to make breakpoints impossible to use, you've likely got enough log output that you're not going to be able to parse. You're almost certainly going to look for other ways of narrowing the search space.
> stopping one thread dead for long enough for a human to investigate it can inadvertently resolve all sorts of race conditions.
Stopping one thread for long enough to do console IO has the same effect. Especially if you're using python, you'll need a lock to synchronise the print statement across your threads!
Once or twice? Any sane debugger has a way to disable the breakpoint.
In my experience, debuggers are really good to expose hidden control flow. But usually, I know the flow, and using a debugger for human-in-the-middle print statements is just going to slow me down. Worse, those print statements are ephemeral, so I'm disinclined to write a nice formatter.
Print debugging leverages the language -- want to print in a certain condition? Easy. Have a recursive structure, an array, or a graph that you need to investigate? A naked print sucks, a custom formatter is great. Need to check some preconditions/postconditions? Do that in code. Don't try to check that stuff by hand in the debugger.
Speaking personally... the only thing I like about icecream is that ic(foo) both prints and returns foo, because you can inject it into code basically for free. But I already have a solution to that:
def ic(x): print(x); return xThe main argument that I have seen is that in print debugging you are relying on the program being executed in a non-descriptive/non-declarative fashion.
I legitimately believe print debugging is incredibly powerful (With a simple print I can check if a function is being called, how many times, if the value has the value I expected and the only requirement I need is to be able to see the stdout of the process. I say that is fantastic!
The real world is all about cost analysis. How much value can I get from a tool vs setup and running cost. The cost of print debugging is incredibly small.
Breakpoints are way worse on this dimension.
Debug printing also allows you to debug programs running in environments where you can't attach a debugger. For example, maybe halting the program causes the bug not to trigger. Or it's a remote system where you cannot attach a debugger for various reasons. Or the bug only happens in the optimized build, which in say C/C++ can make it quite tedious to walk through with a debugger.
Most of the time though I use print as "proactive debugging". Having detailed logs available is gold when customer calls with a blocking issue.
Showing function return values automatically was really an eye-opener when I first encountered it.
Generally when I am coding I auto-run the tests on save. This means that to printf-debug I just add a message or two (and if I am coding I might already have a couple of useful ones lying around) and save. Then in less than a second I have a trace trough my program in the terminal. If I want to inspect a different variable I just add another print and run again.
With a debugger I need to kill my auto-run command, run the program, set breakpoints, type to see what variables I want to inspect, maybe switch stack frames, maybe step through a bit.
In my mind printf is like an automated debugger. I just put the info I want to see into the program and it does all of the printing for me. And when I find the problem I can just fix it and I am back to my edit-test cycle.
I'm not saying that there are no use cases for a debugger. For example I find variable-modification breakpoints very useful. As you mentioned if your edit-run cycle is slow then it may be faster to inspect a new variable in the debugger than adding another print statement. But when I just want to inspect values in my program I find printf sufficient and lower overhead. I'm sure part of my problem is that because I rarely use a debugger I am not as efficient, but I also think that printf-debugging is a very effective workflow for a wide variety of issues.
Every proper IDE has a keyboard shortcut for that though.
With a debugger I need to kill my auto-run command, run the program, set breakpoints, type to see what variables I want to inspect
This indeed falls under your 'part of my problem is that because I rarely use a debugger' statement. E.g you could set breakpoints before you save, use auto-debug instead (i.e. launch program under the debugger on save instead of just rnning it - without breakpoints there shouldn't be much of a difference unless it's one of those nasty multithreading bugs), add variables you want to see to the watch window. Or type them anyway if it's a one-time thing. Or use tracepoints. Etc.
I personally keep bouncing back and forth between debugger and printing. All depends on context, but it's definitely worth it getting to know both really well.
More fairly, it is a trade-off with the debugger giving wide visibility into state, but narrow visibility temporally, and logging giving narrow visibility into state (just what you logged), but broad temporal visibility. They both have their place, but I find that logging narrows things down more quickly, while the debugger helps me understand the problem by walking through step-by-step, assuming the problem isn't obvious once narrowed down.
We agree with your critique of traditional debuggers and Pernosco tackles that "temporal visibility" problem head on.
It’s okay though to add verbose logging as a feature.
But just adding some print statements to debug code and remove them afterwards, is dangerous (you release sth different than you debugged).