Live data from Hacker News

The unreasonable effectiveness of print debugging

buttondown.email

151–160 of 366 posts

Re: The unreasonable effectiveness of print debugging

#152
post #132

Earlier quoted context omitted.

Also, in almost all languages debuggers are an afterthought. Take e.g. the situation with Golang, Haskell or Python. Either there is no useful debugger or there is one, but it came late and still cannot debug everything the language does.

Print debugging not (really) working is haskell is... Non-idea, and a bad pairing for bad real debugging. But test cases are usually easier to figure out. Presumably there's a balance discovered by people in big projects but it never seemed as good as normal approaches to me.

Haskell debugging by testing is great for small functions where you can use quickcheck. But larger tests for the more complicated stuff don't work in quickcheck and there isn't much else that one can easily do.

Re: The unreasonable effectiveness of print debugging

#153
> I do want to point out that print debugging has one critical feature that most step-based debuggers don’t have: you can see program state from multiple time steps all at once.

At Google, we have time-traveling debuggers neatly integrated into our cloud IDE: You can step forwards and backwards, you can inspect variables for all the values they've had or will have until program termination, and you can also see all invocations of methods (along with their parameters) that have happened or will happen.

I still use logging for debugging. Cool tech aside, I think what you really need, above everything else, is the fastest possible iteration cycles.

Re: The unreasonable effectiveness of print debugging

#154
post #91
post #73

Probably the most interesting thing about development as a discipline is the near radio silence on how to debug. There is a decided lack of academic success in engaging with debugging as an object that can be studied. There are channels to learn about debugging as a stand-alone topic. Programmers don't often talk about debugging techniques in my experience. For something that takes up the overwhelming bulk of a devel…

Debugging is impossibly difficult to teach. It's much closer to "how to solve an escape room" than it is to "how to build X". Debugging requires deep understanding what you are doing and your system. It's different every time. And while there's a general algorithm you can follow: 1. Guess what's wrong 2. Ask "How would I prove that's wrong?" 3. Try it 4. If bug found, fix, if not go back to 1 How would you teach that…

I think this is the standard algorithm and it's absolutely terrible. People poke at things, which ends up giving a linear search across a possibly huge system. Even if the "guess" is intelligent, it's not like you can trust it. If you actually fully understood the system, you would know what's wrong and you wouldn't be debugging.

Do a bisect instead. The complexity is O(log n). It's probably slower than if you guess right on the very first time, but that's less important. Debugging time is dominated by the worst cases.

1. Do something you're 90% sure will work that's on the path towards your actual goal.

2. If it works, move forward in complexity towards your actual goal. Else, move halfway back to the last working thing.

3. When you've trapped the bug between working and non-working to the point that you understand it, stop.

"The weather data isn't getting logged to the text files. Can I ping the weather servers? Yes. Can I do a get on the report endpoint? Yes. Can I append to a text file? Yes. Can I append a line to the weather log file? No. Ok, that narrows it a lot."

The real point of this is that you should spend most of your time with working code, not non-working code. You methodically increment the difficulty of tasks. This is a much more pleasant experience than fucking around with code that just won't work and you don't know why. Most importantly, it completely avoids all those times you wasted hours chasing a bug because of a tiny assumption. It's sorta like TDD but without the massive test writing overhead.

A modification for the disciplined: give yourself one (1) free pass at just taking a stab at the answer. This saves time on easy fixes. "Oh, it must have been that the country setting in the config file is off." Give it a single check. And if it's not that, go back to the slow and steady mode. Cause you don't understand the system as well as you thought.

Re: The unreasonable effectiveness of print debugging

#155
I recently discovered a Linux debugger & tool which allowed me to solve problems 10x faster than print statements: pernos.co (which is layered over Mozilla's rr time-tracking debugger).

Pernosco's tool is described pretty well on their website, but basically it allows you to view a program inside and out, forwards /and/ backwards, with zero replay lag. Everything from stack traces to variable displays (at any point in time in your code execution) is extremely easy to view and understand. The best part is the lightning fast search functionality (again: zero lag).

On top of this: extraordinary customer service if anything breaks (in my experience, they fix bugs within 24 hours and are highly communicative).

If you value your time I highly recommend you check out this tool.

Re: The unreasonable effectiveness of print debugging

#156

Personally, I think my biggest reason for using print debugging is.. it works. In C++ I often find the debugger doesn't find symbols on projects built with configure/Make. If I have a Java Gradle project I have no idea how to get it into a debugger. Python debuggers always seem fragile. Rust requires I install and use a "rust-gdb" script -- except on my current machine that doesn't work and I don't know why. I'm sure…

As a counter-point, I think there’s an argument that folks don’t spend enough time in the debugger. But there’s a lot of value there and in fact one could use a debugger environment to unit test as even native debuggers have scripting environments. Personally, I think folks should master the debugger _first_ and during all steps learning a programming language. But similar to test-driven-development it’s a different…

The problem is that it's not just a matter of "learning the debugger for Java." In practice there are many different projects that configure debugging many different ways, and it doesn't matter that you know which keys to press in IntelliJ if it will take you an hour to figure out how to attach it to the project. This speaks to OP's point, where it's hard to use a real debugger to casually investigate random projects.

Having said that, it is absolutely a requirement when working on a project for any length of time (especially professionally) to set up and figure out a debugging environment, because it is significantly more productive than printing. But the startup cost is certainly there.

Re: The unreasonable effectiveness of print debugging

#157

Another aspect, where printf debugging can be better than debuggers are use-cases where timing is relevant. Some bugs don't occur when break points stop the program at certain points in time. For completeness is should be added, that there are also cases where the printf can change the performance and make it impossible to find a bug. I think the two methods are complementary and should be use in combination. However…

Debuggers don't have to halt execution on hitting a breakpoint. They can do other things, like print the contents of memory, letting the host system handle the formatting. They're actually usually better for timing-sensitive prints than printf debugging.

That said, most people don't know this is possible (the learning curve issue you mentioned), even though it's an important part of how to use debuggers!

Re: The unreasonable effectiveness of print debugging

#159
I feel like the author gets close to the point but fails to drive it home: step-through debugging is unbelievably cumbersome. During a typical step-through debugging session, 90% of the time is spent on lines you are completely not interested in. Oh, did you accidentally skip the important point because of how tedious it was to keep spamming step-over/step-in? Better start over again. With print debugging, you set up your print statements strategically and -zing-, you get your results back. Feedback loop shorter. 100% of the lines are the ones you are interested in, because you put the print statements there.

I'm still waiting for the feature where you can conditionally stop at some breakpoint -only- if some other breakpoint/watchpoint was crossed over. It's not a conditional breakpoint, because conditional breakpoints can only watch variables, not other breakpoints. You could of course set some variable depending on whether some section was entered and then conditionally break based on that variable. But then you're back to print debugging land, having to manually insert code in order debug the program.

Debuggers are superior when it comes to interrogating the exact state of some variables, as well as the decision paths the program takes. For anything simpler, print debugging simply offers the better developer experience.

Re: The unreasonable effectiveness of print debugging

#160
post #121

Earlier quoted context omitted.

As a counter-point, I think there’s an argument that folks don’t spend enough time in the debugger. But there’s a lot of value there and in fact one could use a debugger environment to unit test as even native debuggers have scripting environments. Personally, I think folks should master the debugger _first_ and during all steps learning a programming language. But similar to test-driven-development it’s a different…

In a world with perfect optimizing compilers that never introduce bugs, we should never "need" print debugging. But that's not where I live, so I'll keep using print debugging. On the other hand... adding print statements can also invalidate certain optimizations (an excellent source of heisenbugs), so I'll never stop using debuggers either

Print debugging is essential in distributed systems. Sitting in the debugger waiting for human input often leads to timeouts and not covering the case you want. Of course, sometimes adding the prints, or even just collecting values to be printed later also changes execution flow, but like do the best you can.
Post reply on HN