Absolutely, I don't understand why using print() or its equivalent in other languages is looked down upon. It's quick way to narrow down the "area of search" before bringing in the big guns.
> It's quick way to narrow down the "area of search" before bringing in the big guns.
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)
Sometimes sticking the debugger into the wheel makes stuff come flying over the handle bars in spectacular ways that have nothing to do with what you wish to observe. You might not even know which wheel to jam the debugger stick into, if the behaviour is complex.
In these cases prints work well as a less intrusive way to get a rough idea of what is going on.
Imagine putting breakpoints in multiple tight loops in the stage of narrowing the search space. Imagine how many times you need to click next. A conditional breakpoint will only help if you know the condition you're looking for, but there's stage before that of "Well, what looks strange during execution".
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.
What I imagine Macha is arguing for is that the cost of using print is extremely small, smaller at least than breakpoints.
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.
Yes, print() and breakpoints are different tools with different uses and there's cases where one is superior to the other. This is why some tools now offer logpoints, which are basically print() inserted via a breakpoint UI rather than in your code where you can forget to remove them
VS Code and Firefox Developer Tools are the two I'm aware of with actual support. Also some tools you can adhoc it as a conditional breakpoint by basically putting "print(whatever); return false" as the condition
> the cost of using print is extremely small, smaller at least than breakpoints.
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.
> Imagine putting breakpoints in multiple tight loops in the stage of narrowing the search space.
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!
Today I was trying to solve the exact scenario in the second example. A multi threaded program had a race condition that would sometimes occur. printing numbers helped a great deal. Might also be that I'm not that proficient with my debugger even though I use that more than anything.
> in pycharm [0] you can even execute your print statements in the debugger if you so wish
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:
The same thing happens all over Software really. Just because a tool is powerful is looked up as superior, or better.
The 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.
Print debugging has all of those features and is natively built into just about every programming language in existence and doesn’t require any additional libraries or tools.
> The 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.
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.
When I use a debugger I often feel like I'm looking through a soda straw. I can only see the state at that one instance in time. Just because I know the line of code where the exception occurred, doesn't tell me which data caused it, and breaking on exceptions is often too late. Instead I'm stuck hitting continue over and over until I finally see something out of place, realize I went to far and have to start over again. With logging, I have the entire history at my fingertips which I can skim or grep to quickly pinpoint the data that caused things to go wrong.
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.
Would be nice if instead of break points debuggers had log points which stored the values of variables at that point in time. This data can be displayed as a table later.
I more or less agree; but I find myself wondering why I so often use Matlab's debugger, but almost never use pdb for python. It is not like I'm not used to using command line tools (I use bash, git, emacs, etc. everyday). It could just be an accident of habit, I don't know.
Depending on how complex your debugger is, it allows you to output values that might not be inspectable through the debugger. Especially computed values.
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.
You have to first configure your IDE/editor to allow you debugging. This is different for every programming language/environment. Print works in any language without prior configuration.
In the time it takes me to figure out how to connect a debugger to the process I've had a good half-dozen full loops of 1) add print statements 2) compile 3) run already done.
As opposed to software breakpoints which change your compiled binary at runtime in order to debug it. Even if you're using hardware breakpoints you're still changing what the CPU is doing and can easily make multi-threading bugs disappear.
I do it in a different branch & discard it afterwards. Having said that, I never meant print() should be used in place of a proper debugger. All I am saying is they both can complement each other and each one has its place & value. As for me, I find it quicker to add a few print statements and get a rough idea before firing up a debugger(if required). May be others are more proficient in using debuggers. But print() works for me.
Depending on the use case it can be a sign that they don't understand how to debug efficiently. Not that this is something you should judge someone for.
The old style printf from C is still the best formatting tool for output/debugging. The C++ style was just a distraction without introducing anything of real value. log4xyz has some nice features in terms of enabling/disabling at runtime, through a config, but ultimately, printf rules.
The value introduced by C++ was type safety. In C, it's way too easy for the format string to get out of sync with the type of the arguments, e.g.:
printf("%d", my_long_var);
Might seem correct and work correctly on one platform, but fail on another. scanf() is arguably even worse since it can cause memory corruption.
These days compilers have diagnostics to catch those errors, but if you rely on those you can't use dynamic format strings, which means you're effectively using a subset of C with a stronger type checker than C. That's a pretty good state but it's definitely not "old style printf()"; old style printf() was insecure.
And don't get me started on the convoluted macro invocations necessary to correctly convert int32_t, int64_t, size_t and ptrdiff_t. And that's with the newest standard: IIRC there was no standard way to print long long in C, at the time when C++ already supported it.
Maybe C++ fixed type safety, but it introduced lot of complexity and bugs for no actual added value. For instance, because of stateful ios objects, it's close to impossible to write correct code outputing hex on first attempt. I'm sure that lot of C++ code outputing hex is just plain wrong.
Given that C++ keeps getting more and more complex features, it is just amazing that C++ I/O is still so inconvenient, opaque and ultra-verbose.
This appears to work until someone change the alignment to left somewhere in the code. Hence the correct code is:
// C++ type-safe equiv to printf("%-02x",my_int) - it's called progress
std::cout << std::right << std::setfill('0') << std::setw(2) << std::hex << my_int << std::dec << std::endl;
Also, is it relevant to keep the final 'dec' when we assume we can't assert the ios left/right state, so why could we assert the hex/dec state? Or maybe was it a bug to change alignment to left, and not restore it to right afterwards? Or maybe should you restore ios state in some centrol place, and never pass your iostream to some external libs? Discussions and wars ahead. Note that the bug above is very nasty because it will change say "02" into "20", which looks perfectly valid.
Note: I just noticed that in C++20, there is new formatted string proposal. You can't stop progress, but neither can you speed it up it seems.
Note2: the 'std::' spam is yet another indication that C++ is lacking common sense and utterly broken.
The old style printf from C is still the best formatting tool for output/debugging.
The idea that you can just drop this anywhere, sure, that is good. But once you've used string interpolation printf isn't so attractive anymore. No more forgetting arguments, wrong argument order, wrong specifier, ...
Agreed, it's the simplest way to test and validate specific assumptions. Debuggers are useful tools, but it takes you just as long but usually longer to get to the same answer: is what I think is happening here actually happening here?
At least debuggers give you new powers, the posted above makes you install a library, import it all just so you can have slightly cleaner print statements... I'm not gonna go out of my way to import something and have to clean it up after just so my printed statement is better formatted. And if I needed the more powerful debugging, I'd use a debugger not this library.
Sure, but typing
ic(someVariable)
is a lot faster for me than typing
print(f"{someVariable=}")
in Python3.8 or >, and still faster than typing
print(f"someVariable={someVariable}")
in Python < 3.8 (which I still need to use in some cases).
It's especially faster when I think about how often I fat-finger the '{' (and '}', when my editor doesn't insert the matching brace automagically). Of course YMMV.
I (and the person I replied to, I suspect) interpreted the title to mean that you shouldn't debug by printing stuff to the console at all, but instead do some other thing.
I am the same, it's the most easy. Very interesting is that in the laravel php world currently an interesting product is gaining momentum: Ray (https://myray.app/)
So basically it's dump with a lot of neat extras and instead of looking at the console of the script, or the website you are printing on you push this to a little desktop application, from every of your languages you are using. Something like log collection for everything on your desktop.
Hmmm I would have thought that too, but recently I’ve been using byebug, which has changed my mind. Being able to throw in ‘byebug’ on a line, then catch execution at that point in another terminal (using byebug remote) and then check variables at that point, is a game changer. Saves so much time compared to looking for printed statements in the output, and then trying again.
It's a failure of debuggers that they haven't cleared the very low bar of obviousness and ease of use of print(). I'm very much a novice, but RStudio was the first environment that made debugging so easy I didn't feel the need to use print().
My objection here is that code should be self-explanatory, and icecream or ic() doesn't explain itself, so at least I'd prefer a name like icecream_debugger and replace ic() with pr(), perhaps.
After having developed in an environment where I couldn't use a debugger (kernel drivers) I actually think that debugging with prints is better than a debugger most of the time, as it forces you to think about the code and where the failure might be. Right now I only use a debugger when I'm in C++ and I want to get a stack trace for a segmentation fault. In almost all of the other cases I get a broad location with the logging statements (always there), think about what could be happening and then put some prints to test it. Even for memory corruptions I don't use debuggers now, the address sanitizers in clang plus valgrind do the job far better.
I have debugger set in pycharm and use it all the time. I also use print all the time, often along with the debug tool. They are very complimentary and neither tool can do everything the other can.
TBF if you always run your programs in pycharm and use its debugger, you can trivially use non-suspending "evaluate and log" breakpoints instead of print.
> But prints work equally well in any environment.
GP say they "have debugger set in pycharm and use it all the time". So under the assumption (explicitly made in my comment) that they're always using PyCharm to run their program, that's not a concern.
> I can remove prints by just checking out the latest version of the file.
Thereby losing all the changes you've made while observing program behaviour, which may be less than desirable.
Meanwhile it's just as easy if not easier to disable or delete breakpoints from the View Breakpoints pane / window: https://i1.wp.com/cdn-images-1.medium.com/max/800/1*0wAP-w-a... you can just uncheck the "Python Line Breakpoints" box, or select all breakpoints and [Delete] them.
> This is way more efficient way to work for complex situations.
It's also way more inconvenient for simple situations or when trying to sift through in order to zero-in on the issue's rough location, spatial or temporal: unless the debugger is well integrated into the editor it requires syncing multiple sources of information (the debugger's breakpoints configuration and the actual source) — and resynchronising on every edit; and if the debugger is well integrated into the editor… now I'm locked into a specific editor.