Ask HN: Do You Use a Debugger?
It's been years since I've used a debugger (Turbo Debug), never bothered to learn how to use a debugger for non-assembly code, and tend to just sprinkle `print` everywhere until I can narrow down the problem.
I'm a Vim person, and am envious of IDE users with integrated breakpoints, stepping, and variable inspection with just a mouse, but only today found out about Vim's `termdebug` :headdesk:
So I'm wondering... what does the HN crowd use to investigate code problems:
- sprinkling print
- IDE with integrated debugger
- termdebug (i.e editor with gdb in another pane)
- debugger by hand on the command line
- other (please comment)
106 comments
[ 2.9 ms ] story [ 153 ms ] threadI’ll also use print debugging if I feel the situation isn’t worth bringing in a debugger, primarily one I don’t know well yet (see above).
IDE with integrated debugger for larger programs (mostly C# and some Java experience here).
gdb (or similar) for programs without an IDE and larger than a small one-off program.
Some languages also lend themselves (or their implementations do) to better debugging experiences. Common Lisp debugging, for instance, is almost a joy. For grins, I implemented the VM for the Synacor Challenge [0] in tandem with SBCL's debugger. After parsing the opcode, the program entered an ecase. If there was no entry for the opcode, it brought up the debugger, I extended the program, recompiled whatever needed to be recompiled, and resumed it. Wash, rinse, repeat. Once every opcode was implemented it then went on to run the image and I could finish the challenge.
[0] https://challenge.synacor.com/
EDIT: I originally wrote this on my phone and while in a bit of a rush. More relaxed today.
I learned plain (CLI) gdb in college, it was taught as part of an exercise in the 3rd CS course and I found it useful during the course. Though I also still largely used printf debugging back then. At some point I had to debug embedded systems that I could connect to via a serial terminal that had gdb running on them. A GUI was not feasible, so I really learned gdb at that point and found I remembered it well enough to not need another interface. But yes, a GUI would be nice especially if it's a tool you use infrequently (like it would be for me now, I don't do much in the way of professional C or C++ programming that would benefit from gdb knowledge at this point, or not on systems where it can be used).
Most times either a print statement or the equivalent of an assertion will be enough to spot the problem.
Some stuff need to be coded in python though, and then sometimes I use pdb.
I read the code and think through it to form a hypothesis, then use print/error logging to test the hypothesis and come up with a fix.
I haven't used a real debugger in a long time.
Mainly I use the 'xlog' function. I can then wander about the logging file (using a text-editor for navigation) looking at various 'befores' and 'afters'.
I use a bit-mapped variable 'debug' which allows or disallows the logging statements accordingly.
For example, in an emulator, with debug only configured for floppy-disk-controller logging, and looking at the floppy-disk controller code, we might see:
which would be logged, and which would not.As for using 'gdb', I tend to use that only when I have no clue, or a wrong clue, where the code is breaking down.
I tend to leave the logging code around if it's not particularly obstructive or long-winded as I can switch it out at will by setting 'debug' to zero.
Logging gives a solid background of stuff you may not have thought about specifically. It is often the first clue that 'something is rotten in the state of Denmark' when the values that show up are not what they should be.
In truth, debugging is a detective-story, and anything that helps, be it debuggers or logging or printf, is grist for the mill.
With a debugger I can place the probes and do my own printfs instead.
It's not one or the other. You are free to use both, logging and a debugger.
The thing is, it always just works. I don't need to think about it. I write code in 5+ languages fairly regularly, with some frameworks that have really weird... "support" for dev tools.
But maybe I am just a bad developer with bad excuses. If anyone has been in my shoes and seen the light, would love to hear how you manage jumping in to multiple different languages and keeping a consistent debugging experience (or, at least, more consistent than print).
for 80% of the work I'm doing lately it's vscode + LSP + debugger + neovim as a backend.
But if you can get your hands on a "good" debugger with (e.g.) time travel or reverse debugging, there are entire classes of issues you can instantly solve. It's like having superpowers.
It is possible to build a debugger that does both; Pernosco does. (Disclaimer: my baby.) https://pernos.co/about/basics https://pernos.co/about/expressions
I use debug print statements to monitor running programs while doing end to end tests.
I have functions to log errors and restart programs in production. Any busfault gets trapped and logged as well. Often just knowing which line of code barfed tells you what happened. Often I also log the calling address off the stack.
I also have a table driven command line interface that I use heavily to poke at and inspect running programs. Having that probably reduces my need for a debugger. Unlike a debugger it's effect on a running program is minimal.
For timing related stuff and debugging bus interfaces I often use a four channel scope.
My debugging tooling of choice is `console.log` when dealing with simple flows: Do an HTTP request and print the value of a variable
I find it lighter (and faster) than attaching a debugger and slowing everything to a halt.
For advanced asynchronous behavior I use Chrome Dev Tools + React Developer Tools + Redux DevTools with these I can inspect any value, profile, time-travel etc..
I'd say the ratio is 80% console.log / 20% dev-tools
If I have no freaking clue why something is going on, I run through a debugger line by line and watch it all. But in all honesty, that is a rare case. It is far more likely that just slowing down and thinking what could possibly cause a result will guide you in the right direction.
The experience is even nicer in the Pycharm IDE, where I can set breakpoints with arbitrary conditional logic just by pointing and clicking. It's so easy it almost feels like cheating.
That said, Pycharm is a heavy beast. Psychologically it's not worth the overhead of opening it or even switching projects within it unless I'm working on something involved for an extended period of time. So figuring out how to do something like this in Neovim has been on my to-do list for a while.
I also use the built-in Python logging framework to help me identify where problems might be happening in the first place.
One particular knotty routine for me was a small synth, I knew I had neither the math ability or c wizardry to develop complex things easily so I made sure there was plenty of scaffolding to assist me. For each 1k of audio output, there is easily 1m of debugging output that I would then "query" with perl, pagenate each step that I could page through, etc, debugging was put in from the start.
Allowing flexible debugging strategies that I could adjust for the difficulties at hand just seems right for me and I find often that different programming constructs need different debugging strategies, and I just don't think debuggers are agile enough to let me adjust.
Its really that I'm just too dumb to get benefits from debuggers, although I'm guessing they may be just perfect for folks smarter than me.
If it's available I would question not using one. If I was pairing with someone who wouldn't use one or didn't know how I would be frustrated.
Before that--almost twenty-five years ago--I figured out how to use the COBOL "animator" to find out why Peoplesoft payroll processing was crashing on us. There was no way in the world to have figured that out without the animator.
These days, though, I mostly use print or logging.debug
that's a debugger UX problem - the debugger should have a way of setting conditional breakpoints, and allow you to evaluate expressions (and optionally print out expressions).
The only time i've found the debugger lacking is when you have to debug concurrent or async code, and the act of debugging changes the execution to either hide or present a different bug.
I find it really valuable that I can just read a text file to spot some patterns even if I don’t know beforehand which patterns I’m looking for.
With gdb you can even script what it needs to do when it hits a breakpoint. So even if it's not built-in, it's scriptable.
edit: You can even trigger a break or commands if a breakpoint hits a certain number of times: https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Breakpo...
So the hit count is definitely stored.
Leveraging DAP lets you have a uniform interface for debugging in multiple languages. (DAP is a sister protocol to the Language Server Protocol which abstracts IDEs from debugger services)
[1] https://github.com/puremourning/vimspector