I had a good long while where I made the debugger my habit & used it well. I have fallen off the path.
Part of the problem is I often am looking at interactions across systems, so just having a bunch of logs is helpful. Versus having a bunch of debuggers open. Learning the logs also helps me get better at debugging prod: using what's available. But I also add in a bunch of random `console.logs()` too.
I'm looking forward to tracing becoming a more regularly used tool. Being able to see a timeline, across services, is hugely powerful. I hope some-day we design languages that either integrate tracing, or, ascending into high-fantasy, languages where the runtime itself starts to be powered by tracing-like structures, where the tracing also serves like event-sourcing for your galaxy. Innovating on language capabilities, rather than just ever-struggling with language syntax & capabilities & types, feels vastly under-explored.
I do, mainly because of the cognitive overhead of setting up (and fixing when necessary) the extra tooling. Integrating my editor with whatever runs my code is just rarely a zero-effort experience
The exception is with JavaScript in a browser, since the browser is both the native environment where the code is running, and can show the highlighted code and let me set breakpoints. However, even then, more and more of my JS code takes the form of expressions, not procedural statements, and breakpoints just don't help much with those. So in practice I still use them rarely
> However, even then, more and more of my JS code takes the form of expressions, not procedural statements, and breakpoints just don't help much with those
React-style code ends up being more functional, with a lot of just nested expressions, and the concept of a "break point" barely even makes sense in those contexts. If you want to see intermediate values you'd have to first break them out into named constants, and even then there'd be little point to "stepping" (vs just printing) because they're going to be stateless
I might use a stepping debugger on the odd procedural/stateful code, where there are actual variables that change over time, but that's less and less common for me these days
“The most effective debugging tool is still careful thought, coupled with judiciously placed print statements.” — Brian Kernighan, “Unix for Beginners” (1979)
The evolution of a word while it remains still is interesting here: Brian's 1979 "still" is a whole world away from Paul's founder's.
Still, it remains easier to print a value than to reach for gdb. But once you have gdb going, it is easier to do in depth fine surgery with gdb than littering your code with printfs.
Now it seems the two worlds are entangled and I'm confused.
Indeed. In 1979, the debugger in Version 7 Unix, called "adb", was quite primitive by today's standards. For example, you couldn't even set a breakpoint at a specific line of a C function, only at the function's entry point.
On page 314 it says: "C does not generate statement labels. Therefore it is
currently not possible to plant breakpoints at locations other than function entry points without a knowledge of the code generated by the C compiler."
I.e., you'd have to delve into the machine code to find the address of the first machine instruction of your C statement if you wanted to put a breakpoint there.
Try configuring your bundler so it outputs source maps, and disable js transformations aimed at compatibility with old browsers. Like whatever they do with for..of loops and async/await that makes the code unintelligible. For this, put whatever is needed in the configuration of the bundler to target the latest version of Chrome or Firefox (for instance [1] for babel / webpack).
It's worth it really.
And maybe in the future bundlers will not be needed anymore. On recent browsers with HTTP2 and gzip compression, I question the need of bundling at all, especially if the dependency tree is limited (yes, that's a HUGE if). With HTTP2 multiplexing / connections kept alive, Good old <script> tags might work quite well now (with type="module").
This is often laughed at, but I think this is fine. Whatever helps debugging efficiently.
And debuggers have their uses too.
Printing is my usual way of debugging. I use / switch between various languages that have unequal support for interactive debuggers with different interfaces, in various contexts where debuggers are not always usable. Printing / logging always works the same regardless of the language and the context.
Step by step often looses me, and print forces to think about what and where to print so it also has its advantages.
Interactive debugging has a big advantage: no need to recompile / reload each time you need to check out the value of a variable at a new location in the code.
But yes, the fact I switch between languages and contexts seems to encourage me to use generic tools that work okay for every language and context instead of specialized tools, so Kate and printing it is. But you can become efficient anyway, and for a task where printing is not enough, you can always spin up the heavy tools.
> But you can become efficient anyway, and for a task where printing is not enough, you can always spin up the heavy tools.
this exactly.
Print based debugging is a great way to verify all sorts of assumptions, and it gives a wonderful order based view of execution very quickly.
If that turns out not to be enough - by all means, break out the real debugging tools, but I'd wager 95% of the time, print based debugging will lead you to the answer more quickly and easily than spinning up gdb or inspect-brk or visual-studio or any of the other heavy weight tools.
Not that those tools aren't incredible (I mean that so literally) - it's just that they're like bringing a back-hoe in to plant your tulip bulbs... it's complete overkill for answering questions like "Did this code even run?" or "how many times is this function getting called" or "is function a called before function b" and SO many other useful basic tests for my assumptions.
Agreed. I forgot how to use gdb and windbg because it's been so long since I needed to grok assembly to troubleshoot something (you need to use gdb with Go too) and most of my work these days is with interpreted languages that are easy to reflect into, but its very useful knowledge to have in the toolbox.
Step by step often looses me, and print forces to think about what and where to print so it also has its advantages.
This is a very good point. I have taught beginners before, and saw many of them would somehow open the debugger in their IDE, then start stepping through all their code absentmindedly, as if the mere act of doing so would somehow cause the bug to become apparent. By the time they see something wrong, they've already gone well past the point at which the first error was. I have called this phenomenon "debugger tunnel-vision". It seems like the debugger is more like a ritual to them, some sort of magic that will automatically find bugs quickly and easily, and they get immensely confused when that strategy obviously doesn't work.
I turn them towards using prints, and many times they find the bug even before the next run of the program, while considering where and what to print.
IMHO the "tool fetish" that some programmers have is actually counterproductive.
You didn't get the point. My juniors work around fallout in some detail instead of solving the root cause or higher level systemic issue by just a little bit of thinking.
Sure, there is the right time for a tool, but tbh all of my bugs are usually much quicker solved by thinking, understanding, unit tests with maybe a print than trying to catch something with breakpoints and stepping.
And for the other nasty situations (think about concurrency, multiprocess, embedded irq handling) they are not even able to help but only professional tracing is (print-debugging always sounds so amateurish).
So
> Tools help you be more productive. The keystroke reduction in using debuggers far supercedes print debugging
Sometimes yes, but in general I have seen those tools get into the way. For some reason I rarely need them and juniors often get mislead and don't see the forest for the trees.
I very much agree with this (and I tend to fall into the debugger tunnel vision trap!). I therefore start by reading the logs and code, and if this doesn't work (it often does with a little practice), I use print statements.
I have found that reverse debuggers (rr, pernosco) fix the tunnel vision issue by forcing you to identify something wrong ('no, this shouldn't happen') and then letting you work your way up, constantly asking questions ('ok, where is the next thing that doesn't make sense') instead of passively hitting F8.
Basically, debugging is an active process, which requires a proper strategy, and print, code reading, and reverse debuggers all force you to have/design a strategy. Regular forward debugging doesn't in many cases, especially when you are not familiar with the code or the usual strategies you apply don't work in your slightly devious particular case.
There's only been a few times in my 15+ year career that the debugger would have gotten me to an answer faster than printing. I tried it, and got good enough at it to understand what it does for me, and I didn't prefer it.
I really dislike these articles that look down on people for doing things their own way when that way isn't actually wrong. There's a lot going on when programming, and there's very rarely a decidedly wrong answer.
For me the big thing is that there is now a _written record_ of what happened that I can now reference and think about for as long as I want while looking at the code that produces it, which can really help me figure out what exactly is happening. I have found debuggers to be completely useless for understanding how a series of steps has gone subtly wrong, but very useful for figuring out what the fuck is in some giant object I know nothing about.
Why would you debug by hand when you can use a computer? Meant in favor of print of course.
Thinking as I write it probably means it's the print statement that should be improved for debugging if anything. Imagine a debugger that collect them, with the memory / variables and stuff. Could also be turned into break points. Alternatively, could a newly developed terminal be coupled with a debugger like that?
Sometimes I'll just log damn near everything with with debug! from rust's log crate so if I need to debug I can just change the log level and I don't need to remove it later (except for sensitive data).
I find the distinction between step debugging and runtime debugging to be more useful.
Someone who steps through every line of code will probably take longer to find the bug, and will spin cycles on rabbit holes and false paths.
Someone who uses “runtime debugging”, or rather has a hypothesis on the bug or what a piece of code is doing and uses a debugger or print statement to verify that will find the bug much quicker.
With this mental model, you can see how print statements can be as effective as a debugger in many scenarios.
Totally agree. Print statements are windows to the programs internal state. They can be activated during specific times, under certain conditions, can group together information and values.... The name "print statement" does not do them justice , that is the problem i think, they are a conduit, a channel of symbols for peeking into the program
So can a debugger, except when you find something unusual, a real debugger will allow you to inspect things you didn't consider instrumenting, change variable-values, re-run code and validate assumptions.
I just don’t see where this skepticism against specialised tools for tracking down bugs comes from.
If your runtime is in any sort of framework, step debugging often drops you into framework code that you never look at that exists between your logic. This is my biggest hang up when trying to step debug beyond a single method or class.
Most of the debuggers I ever tried have a "Debug my code only" option or something similar and they usually work well (even for C/C++ where you can specify paths to ignore). Have you tried those?
I think its fine to use print in some cases, e.g. quick debug. Google cloud function logging actually even translates it to an info record if I am not mistaking.
I have always loved this method of debugging because printing messages outputs a chronological summary spanning the entire execution, and only relevant information is included.
I used to debug frequently when I coded on Win32 GUIs 20 years ago and running the debugger was as simple as pressing F5 instead of F9. Nowadays, with a multitude of languages, setups and editors, I don't even bother. Even setting up JS debugging under VSCode, which supposedly is a core feature, has always been an exercise in frustration every time I've tried it.
Rarely I run gdb when I'm debugging a particularly hairy C issue, but with any other language I don't even bother and add prints everywhere. The JS "debugger" statement is pretty useful, but it's still very awkward compared to the golden standard of Microsoft IDEs from two decades ago.
Don't blame the users, blame the tooling, editor and language developers that never cared for delivering a good debugging experience.
Visual Studio is plainly the best IDE I've ever worked in. It may be sluggish at times, but whether I was coding in C#, C++ or Python, autocompletion, debugging, and refactoring always worked really well.
The only problem is my current projects are all on Linux.
My current role is mostly done by Windows people (my direct boss is a Windows guy, although his boss runs Linux at work) and I took it while interactions were minimised due to COVID so we decided I should run Windows on the work laptop.
So I figured "When in Rome" and I have Visual Studio and I expected I would come away saying this is pretty cool but I want a modal editor.
Nope, it's terrible. It does have a debugger that is better than I'm used to, but I spend very little time in a debugger, what we're doing isn't rocket surgery. Visual Studio apparently doesn't understand how git works or doesn't understand how files work, so e.g. I'll modify somefile.cs in one tab to fix something, compile, verify my fix worked, go to the Git tab and Visual Studio is wide-eyed. What could I want? There aren't any changes. Yes there are VS, you just saved them, to a file.
So I exit VS and restart it. "Oh!" realises Visual Studio, somebody changed the file. Yes, it was you. You wrote to the file, when I hit save. That's what files are Visual Studio, why is this hard? Now I can commit my change.
C# is a much better language than I expected, PowerShell is a much worse language than I expected. Lots of surprises, good and bad, but I think "Visual Studio is bad at the thing it's for" was the largest surprise.
Although that's mostly because BBS was ahead of its time rather than because of the topic specifically. BBS looks a lot more like how you'd design such a thing if you started out in an era with the Web, rather than dusty printed journals, even though BBS actually is a printed journal and started in 1978.
Anyway, Stevan Harnad actually taught a class I took (as a postgrad, so not pass-or-fail, I was just showing up because it was interesting). Cognitive Science is definitely Brain Science, but it's true that I am not employed to actually do that.
Strong disagree, I use Visual Studio daily, and git integration works great in 2022.
It used to suck not unlike you describe and there is a slight hidden gotcha - you need to select git as your source code provider in the settings as VS supports other version control systems as well.
VS is great for .NET dev, but stay far far far away from its git integration. It’s always sucked. Even if JetBrain’s git tooling is infinitely better, personally I still prefer using the command line because I don’t trust a UI to do the right thing with respect to git.
I use IDEs extensively but never the part where it integrates with the git. This is where the command line / specialized git frontend suite me just fine.
I don't disagree, but borland c++ and turbo pascal both had excellent debuggers, the latter is still working under the guise of lazarus (but i'm not a fan of the gui). What spoiled me in vc++ was remote debugging, statement completion & type/object browsers.
Dropping in a import pdb; pdb.set_trace() in my pytests and running them from the terminal has helped me squash some pretty difficult bugs. It’s dead simple (same with Ruby pry)
I also wanna give a shout out to pudb, for those who want a more visual experience. It uses curses to offer a GUI-like debugging experience while on the terminal. Real nice piece of software.
Now that I think about it I bet you could attach pdb to a process running the python interpreter and find your way to the right level of abstraction. Does not sound practical though
JavaScript's remote debugging protocol is a total game changer. Highly recommend looking into it.
And yes you can use it on frontend stuff if you invoke your browser correctly
And yes you can use it to debug mobile through chrome's inspect feature. And yes you can proxy that to use it in your editor of choice and yes GUD with emacs is supported
And yes the "debugger" keyword correctly triggers the breakpoint through this entire pipeline. It's borderline magic
The concept of remote debugging is just one of instrumentation, that's from the 1960s, this isn't a conceptually new thing. It's a functionally useful one
The issue with JavaScript debugging is you are often using an ad hoc pipeline of transpilers. So even if you pause at a debugger Keyword, the code may look completely different. It takes work to get source maps to work correctly.
ndb definitely stills works for me on Mac. But these days, I just use VS code. Open a special JS terminal (https://imgur.com/a/nqEFYkz), then the debugger will auto-attach to any Node process run in that terminal.
It even works in prod: send the appropriate signal to a node process, pipe the default debug port through ssh and open your node devtools locally. This has saved me so many hours.
> Using print is a sign tooling isn't up for the task. Not necessarily inexperience.
This. For application developers, if your users aren't taking advantage of features in your software, it's a sign the features either aren't actually better, or aren't intuitive enough to gain traction. It's a tell that you still have work to do.
I don't see why the same arguments wouldn't also apply to tools for developers like debuggers. Just because your userbase is more technically savvy doesn't mean you shouldn't care about ease of use.
A print will quite often make the race issue disappear as well (due to it being synced in many cases). The JVM’s flight recorder may be a better option to fight such an issue.
A debugger can work in many ways, it can also print, or trigger on altered memory. I would say that a debugger is generally more useful for finding race conditions than logging. But writing tests is really the best tool there.
In my opinion printing or logging is much more useful than debugging when you want to be able to understand the sequence of events leading up to a bug, where the debugger isn't very strong. After analyzing the logs, using conditional breakpoints depending on each other can help you catch the issue live.
Each tool has their strength and weakness, but I would never go so far as to say that debuggers are pointless. Being able to inspect memory and browse for inconsistencies can be priceless.
A common thing I do is find the problematic code using the IntelliJ debugger, change the code, press Cmd+F9 to reload the function code, hit "drop frame" so the executing code goes back to the previous function in the stack (the caller function) so the edited function can execute again with the new code.
While at it, you can also change the value of any visible variable and evaluate any expression in the context of the currently executing scope.
Python and Node both let you debug with breakpoints and inspections and what not, if you're in IntelliJ. Though far inferior to their Java support, of course. Both in terms of the actual debugging experience and in getting it to run as such.
In Python the IDE doesn't matter as much, because if the support is bad, you can always fall back on pdb.
In fact, I often use pdb anyway because it is sometime just quicker to run, and the code execute faster under it. Also, being able to execute any python code in a shell like env is super nice, and the concept of having commands instead of looking for a button has the same pros/cons than the cli.
I wish there was a 'here be dragons' statement that you could put and it would run a function or a snippet on a special debugging mode (without the need for a debugging tool). And then ask questions like "who set this to value X" or "just print the local vars here" etc
I think the closest we have to this are the debugging environments that (for example) Flask gives you (maybe other frameworks)
There's a stack frame and you can watch on variables to trigger the debugger when they change.
But there's no operation tracing. I'm not aware of any language/system that automatically logs all operations with their context/value and boils that down to a useful display.
Debuggers seem to all use the ancient machine code model where they replace an instruction with "And now launch the debugger to see what state the variables, memory, and registers are."
It would be useful to have more print-like features that create a searchable list of all entries/exits/creators/destructors in sequence with associated parameters and values.
> Don't blame the users, blame the tooling, editor and language developers that never cared for delivering a good debugging experience.
Do blame the users. If they would've said "nah, I'm not using your programming language, it doesn't have a decent IDE and debugger", life would be much better.
I have a feeling no programming language would ever be used anywhere if we were just as strict with other aspects besides "having decent IDE and debugger"
It's hard to imagine that 20 years ago Delphi and Visual Basic had far superior IDEs and debugging experience running on my 100 MHz IBM PC with 512MB of RAM.
I'm not sure where the ecosystem went wrong, but it's strangely related to the web.
It went wrong, when young nice folks with fresh MBAs realized, that the point of diminishing returns for software quality can be very low. Just throw things into production and maybe catch some of the bigger foobars and let the hockey stick growth take over. Yes, you’ll loose some customers but more will come and it’s just not worth it spending time on shifting the exponent slightly to the left. Your evaluation will depend more on the exciting new features rolled out than the 3-per-cent-lower-than-predicted au growth.
Java IDEs still have the best tooling when it comes to debugging/observability, in my humble opinion so at least progress is not lost in every ecosystem.
I've written a few CPAN modules over the years but have never successfully used perl's interactive debugger (or, honestly, any other interactive debugger).
I do, however, know plenty of brilliant developers who can make interactive debuggers sing and respect different people preferring different approaches.
Note: If any of my perl code upsets the debugger it's because I didn't realise as a result of my preferences and I'll still consider it a bug.
This is slightly offtopic but windows 95 ran on 4mb of ram. 8mb was considered luxurious at the time. Visual Studio 6, launched in 1998, ran fine with 16mb of ram. It had almost all of the debugging features we have today, and it worked flawlessly every time.
Half a gigabyte of ram was, and still is, a gargantuan amount of memory.
win95 booted on 4MB, but nobody would call this running. 8MB was the norm for 486DX2 computers since 92. In 93 you had plenty of computers shipped standard with 16MB, for example this dell 466V https://books.google.com/books?id=XNt4ttAFAiwC&pg=PT2&dq=del... there are 10 computers in this issue being sold with 16MB. By 1995 Pentium with 16MB was standard, with 8MB relegated to bargain bin offers.
In the past you run your whole application as one piece on your machine, you could easily set a simple breakpoint and be done with it, now you have many interdependent components (microservices, 3rd party APIs), stuff running in the browser, stuff running in remote databases. It's like looking for a nail in a haystack. Even worse on production, you need to log every single call everywhere just in case you ever need to trace which random component caused that weird issue the client is complaining about.
That doesn't sound right. The "100 MHz" in the first half is too low for a machine from 20 years ago, and "512MB of RAM" for a modest machine of the same era is too high.
You are right, to be precise it was 1997 and I believe my 100 Mhz IBM PC was the 340 model which actually has 16 MBs of RAM. Running Windows 95/98, Visual Studio, 3D Studio Max, Photoshop, Cubase, etc.
JS debugger was amazing a few years ago. I find it struggles with the transpilation and functional elements of debugging something like a Typescript React app with multiple hooks and async operations. Stepping through the code and setting breakpoints doesn't work as expected in some cases and I revert to just slapping console.logs() everywhere to trace execution.
At least Go debugging in Jetbrains' Goland is a smooth and effective experience, on the other hand. I think their other language IDEs are more or less on par.
I'm at my least productive when I can't use a debugger (Jenkins jobs, nginx config, networking stuff). Everywhere else, getting it set up is my top priority.
* I usually fix the bug right there in the debugger where I find it. It's so much easier to fix logic in a REPL with all the actual variables available then in a slow guess-and-rebuild cycle.
* Being able to inspect complex objects is so much more convenient when you can right-arrow your way through the levels or watch a nested subpath.
* I'm constantly surprised and confused as I make my way through code. Reality contradicts my assumptions at every step so I need the ability to see that to find my way to the bug at all.
* I love log statements, but I typically need the debugger to find the place to add the log statement. There are so many candidates I'd have to add five prints for every one I use.
> I'm constantly surprised and confused as I make my way through code. Reality contradicts my assumptions at every step so I need the ability to see that to find my way to the bug at all.
This is a huge point that a lot of people miss. When I was a junior dev working on some shrink wrap software, I’d submit a change for code review, and my stickler boss would read it and then say “have you single stepped through the main path of this code yet?” I found this annoying, but he made me do it, and god damn it if I didn’t often encounter situations where the code apparently worked, but I caught subtle errors by watching the state change one step st a time. “Oh my god, this value is never even looked at and I’m getting lucky it never fills the buffer on subsequent iterations!” It’s very, very eye opening. It became clear that it’s hubris to read a piece of code and assume I can interpret it faithfully. The debugger gives you another picture of reality.
Sometimes the print statement is the best tool for the job; sometimes it is the only tool for the job. If you know how to debug by adding print statements, running a test, and reading the resulting log, you can solve any problem, in any environment, on any platform[1]. Other tools may well provide faster results in specific situations, but one has to balance the time spent learning & managing fancier tools versus the time spent getting the job done.
1) Well, okay, there are exceptions. Perhaps you have to find a spare IO pin attached to an LED, and make it blink at you in morse code, because your serial port hasn't come up yet, so you can't print to the console; but it's the same idea.
That's a high horse you've got there. There is a vast class of bugs that are not memory bugs and many languages where memory bugs are just not an issue.
Not only that, but the bugs I find most difficult to deal with (like races) I find have a tendency to disappear in a debugger. Tracing output is what's needed here, and that's just a slightly more structured print.
I have debugged plenty of memory bugs without using a debugger.
Debugging multithreaded/async code is an entirely deeper level of hell, and there's been plenty of times where just having a debugger attached was enough to make the code work, or building unoptimised/with debugging information. On the other hand, carefully placed prints can catch the culprit without affecting the timing so much that the bug disappears.
I’d like to see you debug a database migration with a runtime debugger. The issue starts after about two million entries and only affects a subset of them. Have fun!
Is it reproduceable? If so, easy: set a conditional breakpoint on the ones where the problem is known to occur. Step through them. A database migration is a pretty serialized operation. It’s perfect. Having lots of data or lots of iterations is really no burden unless you think debugging means manually stepping through everything.
Let’s say it’s spurious and seemingly resource-dependent; data shape issues etc. would be apparent from the result data and maybe not need that much debugging.
Speaking from experience, a migration is also a pretty buffered operation (especially over a network), and stopping the migration at a breakpoint before the failure occurs will almost certainly give all layers enough time to flush this and that so that the problem magically disappears.
The opposite is also true: debugger changes timings drastically, it removes race conditions, code suddenly works when you run it in debugger, and stops working without.
> but developers are extremely underqualified, I get it.
And the most annoying thing about them is that they don't realize it, and thinks they know it all and calls everyone who doesn't work like they do noobs.
Maybe I'm weird, but when I'm debugging, the problem could be in my code, library code, runtime code, or kernel code. Usually the first two can use the runtime debugger, but the runtime code needs a different debugger (usually gbd/lldb), and the kernel needs a third debugger (if you even get one). And sometimes I care about the frontend, so there'a a web debugger and maybe a windows debugger for the browser. Thankfully, I've never needed to debug the windows kernel, that would be a whole nother level. Sometimes I'm working on projects with multiple languages, so then I need to debug in Erlang, Perl, PHP, Javascript, hopefully not much Python, userland C, kernel level C. My degree from the printf school of debugging works on all of those. But still, sometimes, printf isn't enough, and you've got to spend the half day figuring out how to get the debugger to work in your current environment, and then the situation often just pops right out (but sometimes it doesn't and you're still just wondering... but now in a much more powerful and dangerous tool).
Personally, tcpdump is my favorite debug hammer, but printf is second. FreeBSD's kernel debugger is pretty neat (especially over an IPMI serial console), but fills me with dread when I need it.
Sometimes even poking a spare IO pin is insufficient!
I once encountered a hardware issue where a single instruction was being executed twice and this was only revealed by routing the least significant bits of the program counter onto IOs. Only after counting the number of cycles that the offending instruction took revealed the issue.
>Perhaps you have to find a spare IO pin attached to an LED
I made a write-only UART on an ST7. Clocking worked out to 500kbit because that was the first configuration I found where I could make the mark and space bits take an equals number of cycles. I still remember thinking: why in the world is the timing for each of these instructions so different?
I found that using the final pixel color as your “printf” works wonders. You can turn any proposition into a color and directly see the debug pixel values on screen. A simple example is displaying your normals as RGB.
I haven't coded for a while, but here's a weird situation where print() worked better in some cases for me -- you know how people say "I have better learning retention when I take notes by hand"? I found that sometimes the act of writing the print() statement helped to focus my mind and think about what I was trying to debug. For example, what specific aspects of the data structure do I need to see and why? How do I want to frame the relationship between two or more variables? Stuff like that would sometimes lead to the discovery of the bug before I even completed the print() line.
Seeing the state is faster with a debugger is probably faster, but the process is an analog to explaining the situation to a rubber duck.
I like printf-debugging because it's usually the fastest and has the least mental overhead. Even with IDEs and integrated debugging I find there's often a lot more overhead.
And when I do use a debugger I always just use it to inspect variables; quicker to just print those same variables: less steps, and especially works better if the same code will be run 10 times and I want to print those vars 10 times too.
I never found stepping over code useful in a debugger, and I tried to use it many times because surely it's useful for some, but turns out it's not for me.
I have an extensive test suite (10000+ tests) and zero bugs in production for the last 5+ years. I never use debuggers. Why manually do what software can do automatically? That doesn’t make sense.
Debugger in chrome dev tools is easy enough though as I get older I find myself actually preferring console log, or similar strategies, again. Especially if the function in question get invoked multiple times, printing seems easier
I don't understand the cleverness here. How else would you start debugging? If you're not a IDE user, print is your first course of action. This is like gufawing about a doctor using a barium swallow or an x-ray.
I've found tests to be my new print debug.
Too complicated of an internal state to get at what's going on? Write a test. The failing test will tell you the output.
When debugging Xcode projects I 100% use the debugger - even when doing print debugging (for e.g. hard to pinpoint UI bugs) I'll use print breakpoints since you can add/remove/edit those without recompiling/relaunching.
When using something like JavaScript or PHP where the tooling is finicky and fragile I just give up and use 100% print debugging.
Xcode has really great debugging, though the debugger can be slow to initialise on the first breakpoint
I love that you can hit spacebar on any local variable and "QuickLook" preview it just like a file in the finder. It even renders UIViews, and previews 3D models right in the debugger. The 3D view debugger, memory graph inspector, and all the other tools are just great
Only the first breakpoint? It's glacial for me on a pretty well spec'd 2019 MBP. Every step takes several seconds as, I guess, resources or just data are pulled from my device. I still try to start with the debugger but I almost always fall back to print statements. (It's often faster to add print statements and rebuild+redeploy than wait for a step or two in the debugger).
325 comments
[ 2.7 ms ] story [ 211 ms ] thread"I think the vast majority of developers still debug using print() statements."
— email from a founder
Part of the problem is I often am looking at interactions across systems, so just having a bunch of logs is helpful. Versus having a bunch of debuggers open. Learning the logs also helps me get better at debugging prod: using what's available. But I also add in a bunch of random `console.logs()` too.
I'm looking forward to tracing becoming a more regularly used tool. Being able to see a timeline, across services, is hugely powerful. I hope some-day we design languages that either integrate tracing, or, ascending into high-fantasy, languages where the runtime itself starts to be powered by tracing-like structures, where the tracing also serves like event-sourcing for your galaxy. Innovating on language capabilities, rather than just ever-struggling with language syntax & capabilities & types, feels vastly under-explored.
The exception is with JavaScript in a browser, since the browser is both the native environment where the code is running, and can show the highlighted code and let me set breakpoints. However, even then, more and more of my JS code takes the form of expressions, not procedural statements, and breakpoints just don't help much with those. So in practice I still use them rarely
Would you mind expanding on that?
I might use a stepping debugger on the odd procedural/stateful code, where there are actual variables that change over time, but that's less and less common for me these days
Still, it remains easier to print a value than to reach for gdb. But once you have gdb going, it is easier to do in depth fine surgery with gdb than littering your code with printfs.
Now it seems the two worlds are entangled and I'm confused.
C-x #n
Indeed. In 1979, the debugger in Version 7 Unix, called "adb", was quite primitive by today's standards. For example, you couldn't even set a breakpoint at a specific line of a C function, only at the function's entry point.
For the "adb" documentation, see Volume 2A of the Unix Version 7 doc: https://plan9.io/7thEdMan/v7vol2a.pdf
On page 314 it says: "C does not generate statement labels. Therefore it is currently not possible to plant breakpoints at locations other than function entry points without a knowledge of the code generated by the C compiler."
I.e., you'd have to delve into the machine code to find the address of the first machine instruction of your C statement if you wanted to put a breakpoint there.
Debuggers have come a long way since then.
Everytime I try to step through, there's all these arbitrary access layers that have to be hit.
It's worth it really.
And maybe in the future bundlers will not be needed anymore. On recent browsers with HTTP2 and gzip compression, I question the need of bundling at all, especially if the dependency tree is limited (yes, that's a HUGE if). With HTTP2 multiplexing / connections kept alive, Good old <script> tags might work quite well now (with type="module").
[1] https://webpack.js.org/loaders/babel-loader/#customize-confi...
And debuggers have their uses too.
Printing is my usual way of debugging. I use / switch between various languages that have unequal support for interactive debuggers with different interfaces, in various contexts where debuggers are not always usable. Printing / logging always works the same regardless of the language and the context.
Step by step often looses me, and print forces to think about what and where to print so it also has its advantages.
Interactive debugging has a big advantage: no need to recompile / reload each time you need to check out the value of a variable at a new location in the code.
But yes, the fact I switch between languages and contexts seems to encourage me to use generic tools that work okay for every language and context instead of specialized tools, so Kate and printing it is. But you can become efficient anyway, and for a task where printing is not enough, you can always spin up the heavy tools.
this exactly.
Print based debugging is a great way to verify all sorts of assumptions, and it gives a wonderful order based view of execution very quickly.
If that turns out not to be enough - by all means, break out the real debugging tools, but I'd wager 95% of the time, print based debugging will lead you to the answer more quickly and easily than spinning up gdb or inspect-brk or visual-studio or any of the other heavy weight tools.
Not that those tools aren't incredible (I mean that so literally) - it's just that they're like bringing a back-hoe in to plant your tulip bulbs... it's complete overkill for answering questions like "Did this code even run?" or "how many times is this function getting called" or "is function a called before function b" and SO many other useful basic tests for my assumptions.
This is a very good point. I have taught beginners before, and saw many of them would somehow open the debugger in their IDE, then start stepping through all their code absentmindedly, as if the mere act of doing so would somehow cause the bug to become apparent. By the time they see something wrong, they've already gone well past the point at which the first error was. I have called this phenomenon "debugger tunnel-vision". It seems like the debugger is more like a ritual to them, some sort of magic that will automatically find bugs quickly and easily, and they get immensely confused when that strategy obviously doesn't work.
I turn them towards using prints, and many times they find the bug even before the next run of the program, while considering where and what to print.
IMHO the "tool fetish" that some programmers have is actually counterproductive.
Imo you've blamed tool fetish when it's really inexperience that is the problem.
So
> Tools help you be more productive. The keystroke reduction in using debuggers far supercedes print debugging
Sometimes yes, but in general I have seen those tools get into the way. For some reason I rarely need them and juniors often get mislead and don't see the forest for the trees.
I have found that reverse debuggers (rr, pernosco) fix the tunnel vision issue by forcing you to identify something wrong ('no, this shouldn't happen') and then letting you work your way up, constantly asking questions ('ok, where is the next thing that doesn't make sense') instead of passively hitting F8.
Basically, debugging is an active process, which requires a proper strategy, and print, code reading, and reverse debuggers all force you to have/design a strategy. Regular forward debugging doesn't in many cases, especially when you are not familiar with the code or the usual strategies you apply don't work in your slightly devious particular case.
I really dislike these articles that look down on people for doing things their own way when that way isn't actually wrong. There's a lot going on when programming, and there's very rarely a decidedly wrong answer.
Thinking as I write it probably means it's the print statement that should be improved for debugging if anything. Imagine a debugger that collect them, with the memory / variables and stuff. Could also be turned into break points. Alternatively, could a newly developed terminal be coupled with a debugger like that?
Someone who steps through every line of code will probably take longer to find the bug, and will spin cycles on rabbit holes and false paths.
Someone who uses “runtime debugging”, or rather has a hypothesis on the bug or what a piece of code is doing and uses a debugger or print statement to verify that will find the bug much quicker.
With this mental model, you can see how print statements can be as effective as a debugger in many scenarios.
I just don’t see where this skepticism against specialised tools for tracking down bugs comes from.
Probably from the previous skepticism against print debugging.
Every prevalent strong opinion in a social setting is eventually met with an equally strong opposite opinion in the same social setting.
Rarely I run gdb when I'm debugging a particularly hairy C issue, but with any other language I don't even bother and add prints everywhere. The JS "debugger" statement is pretty useful, but it's still very awkward compared to the golden standard of Microsoft IDEs from two decades ago.
Don't blame the users, blame the tooling, editor and language developers that never cared for delivering a good debugging experience.
Nothing on other platforms combines the power and ease of use.
That's why I quit using it and went back to debugging with print.
Print-debugging limits my cleverness and forces me to think about what's going on.
The only problem is my current projects are all on Linux.
So I figured "When in Rome" and I have Visual Studio and I expected I would come away saying this is pretty cool but I want a modal editor.
Nope, it's terrible. It does have a debugger that is better than I'm used to, but I spend very little time in a debugger, what we're doing isn't rocket surgery. Visual Studio apparently doesn't understand how git works or doesn't understand how files work, so e.g. I'll modify somefile.cs in one tab to fix something, compile, verify my fix worked, go to the Git tab and Visual Studio is wide-eyed. What could I want? There aren't any changes. Yes there are VS, you just saved them, to a file.
So I exit VS and restart it. "Oh!" realises Visual Studio, somebody changed the file. Yes, it was you. You wrote to the file, when I hit save. That's what files are Visual Studio, why is this hard? Now I can commit my change.
C# is a much better language than I expected, PowerShell is a much worse language than I expected. Lots of surprises, good and bad, but I think "Visual Studio is bad at the thing it's for" was the largest surprise.
It sounds like using a second program to add and commit files to git would work better for you.
(I've not spent much time with visual studio)
Nor brain science either, I'd hazard.
https://www.cambridge.org/core/journals/behavioral-and-brain...
Although that's mostly because BBS was ahead of its time rather than because of the topic specifically. BBS looks a lot more like how you'd design such a thing if you started out in an era with the Web, rather than dusty printed journals, even though BBS actually is a printed journal and started in 1978.
Anyway, Stevan Harnad actually taught a class I took (as a postgrad, so not pass-or-fail, I was just showing up because it was interesting). Cognitive Science is definitely Brain Science, but it's true that I am not employed to actually do that.
It used to suck not unlike you describe and there is a slight hidden gotcha - you need to select git as your source code provider in the settings as VS supports other version control systems as well.
For MSVC, I had to write some visualization files (.natvis), phpStorm displays everything just fine.
Also since were on the topic I started using pdb++ and my debugging got incredibly more efficient.
https://github.com/pdbpp/pdbpp
And yes you can use it on frontend stuff if you invoke your browser correctly
And yes you can use it to debug mobile through chrome's inspect feature. And yes you can proxy that to use it in your editor of choice and yes GUD with emacs is supported
And yes the "debugger" keyword correctly triggers the breakpoint through this entire pipeline. It's borderline magic
The concept of remote debugging is just one of instrumentation, that's from the 1960s, this isn't a conceptually new thing. It's a functionally useful one
Thing that's nice with ndb it's the same UI as Chrome devtools that I'm so used to.
Debugging via Webstorm felt too weird (tho probably 2x more powerful). Not a VSCode user.
- you set it up correctly
- your entire stack is compatible from end 2 end
- there is now weird thing with map files
- you don't dev using firefox
It's a lot of if.
So sadly, lots of print(f"")'s
I know it's a problem with Python and Node though.
Using print is a sign tooling isn't up for the task. Not necessarily inexperience.
This. For application developers, if your users aren't taking advantage of features in your software, it's a sign the features either aren't actually better, or aren't intuitive enough to gain traction. It's a tell that you still have work to do.
I don't see why the same arguments wouldn't also apply to tools for developers like debuggers. Just because your userbase is more technically savvy doesn't mean you shouldn't care about ease of use.
In my opinion printing or logging is much more useful than debugging when you want to be able to understand the sequence of events leading up to a bug, where the debugger isn't very strong. After analyzing the logs, using conditional breakpoints depending on each other can help you catch the issue live.
Each tool has their strength and weakness, but I would never go so far as to say that debuggers are pointless. Being able to inspect memory and browse for inconsistencies can be priceless.
Do these support edit and continue?
Last I played with XCode, it didn't.
Major flaw.
Both edit variable and hot reload code.
While at it, you can also change the value of any visible variable and evaluate any expression in the context of the currently executing scope.
In fact, I often use pdb anyway because it is sometime just quicker to run, and the code execute faster under it. Also, being able to execute any python code in a shell like env is super nice, and the concept of having commands instead of looking for a button has the same pros/cons than the cli.
I wish there was a 'here be dragons' statement that you could put and it would run a function or a snippet on a special debugging mode (without the need for a debugging tool). And then ask questions like "who set this to value X" or "just print the local vars here" etc
I think the closest we have to this are the debugging environments that (for example) Flask gives you (maybe other frameworks)
But there's no operation tracing. I'm not aware of any language/system that automatically logs all operations with their context/value and boils that down to a useful display.
Debuggers seem to all use the ancient machine code model where they replace an instruction with "And now launch the debugger to see what state the variables, memory, and registers are."
It would be useful to have more print-like features that create a searchable list of all entries/exits/creators/destructors in sequence with associated parameters and values.
Do blame the users. If they would've said "nah, I'm not using your programming language, it doesn't have a decent IDE and debugger", life would be much better.
I'm not sure where the ecosystem went wrong, but it's strangely related to the web.
What kind of a strange beast was that? :)
I do remember fondly debugging in Delphi, but when using Perl and Python's debuggers I don't really miss it
I do, however, know plenty of brilliant developers who can make interactive debuggers sing and respect different people preferring different approaches.
Note: If any of my perl code upsets the debugger it's because I didn't realise as a result of my preferences and I'll still consider it a bug.
Half a gigabyte of ram was, and still is, a gargantuan amount of memory.
That doesn't sound right. The "100 MHz" in the first half is too low for a machine from 20 years ago, and "512MB of RAM" for a modest machine of the same era is too high.
Point still stands, you don't need 8 GBs.
Insanely fast and capable debugger for Windows. https://www.youtube.com/watch?v=r9eQth4Q5jg
(I have no relation to the project, just think it's great)
* I usually fix the bug right there in the debugger where I find it. It's so much easier to fix logic in a REPL with all the actual variables available then in a slow guess-and-rebuild cycle.
* Being able to inspect complex objects is so much more convenient when you can right-arrow your way through the levels or watch a nested subpath.
* I'm constantly surprised and confused as I make my way through code. Reality contradicts my assumptions at every step so I need the ability to see that to find my way to the bug at all.
* I love log statements, but I typically need the debugger to find the place to add the log statement. There are so many candidates I'd have to add five prints for every one I use.
This is a huge point that a lot of people miss. When I was a junior dev working on some shrink wrap software, I’d submit a change for code review, and my stickler boss would read it and then say “have you single stepped through the main path of this code yet?” I found this annoying, but he made me do it, and god damn it if I didn’t often encounter situations where the code apparently worked, but I caught subtle errors by watching the state change one step st a time. “Oh my god, this value is never even looked at and I’m getting lucky it never fills the buffer on subsequent iterations!” It’s very, very eye opening. It became clear that it’s hubris to read a piece of code and assume I can interpret it faithfully. The debugger gives you another picture of reality.
1) Well, okay, there are exceptions. Perhaps you have to find a spare IO pin attached to an LED, and make it blink at you in morse code, because your serial port hasn't come up yet, so you can't print to the console; but it's the same idea.
Debugging multithreaded/async code is an entirely deeper level of hell, and there's been plenty of times where just having a debugger attached was enough to make the code work, or building unoptimised/with debugging information. On the other hand, carefully placed prints can catch the culprit without affecting the timing so much that the bug disappears.
Speaking from experience, a migration is also a pretty buffered operation (especially over a network), and stopping the migration at a breakpoint before the failure occurs will almost certainly give all layers enough time to flush this and that so that the problem magically disappears.
And the most annoying thing about them is that they don't realize it, and thinks they know it all and calls everyone who doesn't work like they do noobs.
Then you learn debugger like once? twice? thrice? And still they are not this complex
>but one has to balance the time spent learning & managing fancier tools versus the time spent getting the job done.
There is funny saying which goes like this
You can save 2h of reading debugger docs with 12 years of using prints
Personally, tcpdump is my favorite debug hammer, but printf is second. FreeBSD's kernel debugger is pretty neat (especially over an IPMI serial console), but fills me with dread when I need it.
Maybe just a spare IO pin and an oscilloscope.
I made a write-only UART on an ST7. Clocking worked out to 500kbit because that was the first configuration I found where I could make the mark and space bits take an equals number of cycles. I still remember thinking: why in the world is the timing for each of these instructions so different?
Seeing the state is faster with a debugger is probably faster, but the process is an analog to explaining the situation to a rubber duck.
And when I do use a debugger I always just use it to inspect variables; quicker to just print those same variables: less steps, and especially works better if the same code will be run 10 times and I want to print those vars 10 times too.
I never found stepping over code useful in a debugger, and I tried to use it many times because surely it's useful for some, but turns out it's not for me.
When using something like JavaScript or PHP where the tooling is finicky and fragile I just give up and use 100% print debugging.
I love that you can hit spacebar on any local variable and "QuickLook" preview it just like a file in the finder. It even renders UIViews, and previews 3D models right in the debugger. The 3D view debugger, memory graph inspector, and all the other tools are just great