> I consider debuggers to be a drug -- an addiction. Programmers can get into the horrible habbit of depending on the debugger instead of on their brain. IMHO a debugger is a tool of last resort. Once you have exhausted every other avenue of diagnosis, and have given very careful thought to just rewriting the offending code, then you may need a debugger.
https://www.artima.com/weblogs/viewpost.jsp?thread=23476
I'm still baffled by people like the author who "do not use a debugger.' A print statement is a kind of debugger, but one with the distinct disadvantage of only reporting the state you assume to be important.
This part was a little surprising:
> For what I do, I feel that debuggers do not scale. There is only so much time in life. You either write code, or you do something else, like running line-by-line through your code.
I could easily add something else you might spend the valuable time of your life doing: playing guessing games with print statements rather than using a powerful debugger to systematically test all of your assumptions about how the code is running.
Yes, debuggers are useful in some circumstances, but most of the time I don't reach for one. I usually start out by looking at the code surrounding a problem and thinking about what circumstances could lead to the erroneous data. From there, it's pretty simple to stuff in some carefully selected print statements to make it report on some of the things it's doing, so that I can check my assumptions. More often than not, this is enough to identify the underlying problem in a few minutes and fix it. Either that, or I get some pointers to other areas that should be investigated.
It's only when this initial triage fails to give me any meaningful leads that I start thinking about what tool will likely be the most effective for the problem at hand. It's usually a judgment call based on how complicated it will be to separate relevant data from irrelevant, and what tool will most efficiently give a window complete enough to describe what I'm looking at, yet small enough for my brain to hold it all. Also, the need for aggregate vs individual data will play a role in the decision. Sometimes this phase is likely to be faster with a debugger, sometimes (and in my 20 years experience, usually) not.
Coding without a debugger is like walking with your eyes closed or driving at night with no headlights. Sure, it may be possible, but you are purposefully limiting your information in order to not become "dependant" on something.
Tooling will always be a compromise of utility vs reliance but there is a reason we don't, for example, build cars by hand any more.
> ... and I almost never use a debugger.
Even if he can't recall the last time he used it, he keeps the tool in his toolbox.
His article was quite inflammatory however and it's easy to see how someone who relies on a debugger would feel attacked and insulted.
I look around my team and I see some turning to debuggers first and others to outputting at key points. I see zero correlation to effectiveness or efficiency.
I also use intellij for big projects but I prefer emacs/vim for little scripting stuff or tiny programs.
IDEs help navigate and refactor big stuff while editors have less overhead and force you to use your brain a bit more.
I spend a lot of time working with APIs and libraries that are poorly documented and often that I haven't used before.
Instead of writing out a bunch of code based on my limited understanding of the docs, and likely with many bugs, what works for me is to just write a few lines of code, until I get to the first API call I'm not sure about or am just curious about. I add a dummy statement like "x = 1" on the next line and set a breakpoint there.
Then I start the debugger (which conveniently is also my code editor) and hopefully it hits the breakpoint. Now I get to see what that library call really did, with all the data in front of me. Then I'm ready to write the next few lines of code, with another dummy breakpoint statement after that.
Each step along the way, I get to verify if my assumptions are correct. I get to write code with actual data in front of me instead of hoping I understood it correctly.
If I'm writing Python code in one of the IntelliJ family of IDEs, I can also hit Alt+Shift+P to open a REPL in the context of my breakpoint.
Of course this won't work for every kind of code. If I were writing an OS kernel I might use different techniques. But when the work I'm doing lends itself to coding in the debugger, it saves me a lot of time and makes coding more fun.
At my last job for example, I frequently would plug the JTAG in to check the instruction pointer and read from the memory-mapped flash on our safety MCU because there was no other way to read data off the device after a crash.
And it was also an asset in quickly testing the programs I wrote for TI's N2HET because I could pause execution after the programs were loaded into the HET instruction ram, use the debugger to configure the variables in the instruction RAM and set the HET executing and watch the output on my oscilloscope. This ability to manipulate memory in the running program is very useful.
I used it very frequently when testing new components on our system buses because I could halt execution and configure DMA transfers and also inject data into system ram. So I could quickly validate my understanding of the reference manuals.
I think both approaches have merits and if someone tells you that one is superior to the other, that says a lot about their level of experience and the types of work they've done.
> I think both approaches have merits and if someone tells you that one is superior to the other, that says a lot about their level of experience and the types of work they've done.
Indeed. We have so many great tools available to us, but the best choice of tools will vary a lot depending on the work you're doing, whether it is logging, JTAG and a 'scope, or whatever you have available.
It's quite a contrast to this quote from the article:
> ...the fact that Linus Torvalds, who is in charge of a critical piece of our infrastructure made of 15 million lines of code (the Linux kernel), does not use a debugger tells us something about debuggers
I don't think that tells us anything about debuggers, it just tells us that they aren't useful for Linus's particular work, or possibly he just doesn't like them. I doubt that Linus has ever said, "You should never use a debugger regardless of the kind of programming you're doing."
In Java, I did this a lot less because I could rely on the type of the interface to know what I was going to get back from the function call (with the caveat it might be null). I knew exactly what the type of the arguments needed to be.
You generally don't have that in python. Without extensive documentation (if you are lucky some newer code may make use of type annotations) you just have no idea with some interfaces what needs to be passed and what you will receive. Even with extensive documentation, the majority of well documented libraries are all documented in a different way and getting the same answers that the java method signature gives can be a confusing experience.
The tendency of functions to mask the signature of the method they simplify a call to by simply declaring args, *kwargs also makes it a bit annoying to discover the true type signature outside of a debugger.
Also, you get back a dict or a tuple... but what is in it?
There is no denying that seeing is the real world, and imagining is just imagination :).
Some cases where your imagination could be limited are:
- Code that you didn't write.
- Code that you wrote long enough ago that you don't remember the details.
- Code that you know very well, but you're having a bad day and can't figure out the problem just by thinking about it.
For instance, am I suppose to “imagine” how all of AWS Boto3 functions work?
https://boto3.amazonaws.com/v1/documentation/api/latest/inde...
But it can only fit so much so I prioritize hotpaths to live in my imagination.
I've been a professional now for about 15 years and very, very rarely do I get to work on "my code". Almost all of the code I have to work with was written by someone else originally and I have to just modify the system for new requirements. Tests do not exist or if they do they are largely incomplete.
So the only thing to do is to step through find the problem, fix the ticket and move on.
Sure If I get to design the system I normally write it very simply / well structured with appropirate levels of abstraction and with enough tests to expose the bugs in my code. But very rarely do I get paid to work on my code, because my code doesn't need a lot of maintenance. I normally am asked to make changes to bad systems.
> Brian W. Kernighan and Rob Pike wrote that stepping through a program less productive than thinking harder and adding output statements and self-checking code at critical places. Kernighan once wrote that the most effective debugging tool is still careful thought, coupled with judiciously placed print statements.
Thinking harder when I have a project with millions of lines of code (this is normal in large financial systems) won't help me. A debugger will.
I think a lot of these famous programmers have never had to work with something terrible and probably never will and that is why they make such blasé statements.
Next: running a debugger. I use print statements, but seriously, what's the difference? You use watch points in the debugger. Same thing. Once I have tests I find it easier to run them and look at the output of my prints as opposed to stepping through the code. Stepping through the code requires you to remember what you have done before and what the output was (granted debuggers that allow you to go backwards are helpful). If you can do that, then it's all good, but I find that it's easier for me to essentially create a log and read through it. It's just a bit more structured, but in the end it's exactly the same thing.
I think the biggest mistake that less experienced programmers do is that they don't try to reason about the code before they start. It's that isolation that's key, not how you are displaying the state of the program. Single stepping is fine when you've got 100 lines of code, but when you have thousands or millions of lines of code, it's not going to work. You need to be able to work your way backward from the error, reasoning using the source code as a map. You then use debugging tools (or printfs) to narrow down your options.
If I was writing code in c++ I would probably create something awful because I wouldn’t know what I would be doing.
Do you think they never had to work with something terrible, or when faced with something terrible, they took the time to make it not terrible? The track record for the software these two have built speaks for itself.
Those record-and-replay debuggers also fix some of the other big problems with debugging, such as the need to stop a program in order to inspect its state.
The author is right that the traditional debugger feature set leaves a lot to be desired. Where they (and many other developers) go wrong is to assume that feature set (wrapped in a pretty Visual Studio interface) is the pinnacle of what debuggers can be. It's an understandable mistake, since progress in debugging has been so slow, but state-of-the-art record-and-replay debuggers prove them wrong. Furthermore, record-and-replay isn't the pinnacle either; much greater improvements are possible and are coming soon.
I couldn't do my daily work without rr, since often I work with systems that are large and complex and I didn't write myself.
Also, when you're working with code that takes a long time to compile and link, using a debugger to check program state can be a lot quicker than recompiling the code with added print statements.
Different developers work with different types of code in different environments, so just because Linus Torvalds or Rob Pike does something one way doesn't mean that this is the most effective way for everyone else to do it.
Many years ago, we were installing an ERM system, when we discovered that it would not run payroll--or rather, it would run payroll for every employee except for the two who were in a certain jurisdiction. The very expensive consultants who were overseeing the implementation did not have any useful ideas on how to resolve this. (Though they did have a useless and time-consuming one.) I figured out how to use the COBOL debugger (called an "animator"), and in a couple of hours located the problem. Perhaps if I knew how to write COBOL printfs, I could have done without the animator, but I was all but illiterate in COBOL.
I have since inherited the care of a WinForms system, and without the Visual Studio debugger I'd be lost there.
I
Also debuggers display contents of variables very nicely while stepping through code - for me a good way to verify my assumptions after reading a certain piece of code, just set a breakpoint and see if it's actually works that way - I'm often surprised.
Maybe it's not really required for C / low-level code but if you are wrangling complex Java codebases it's a very nice tool to have in the toolbox.
In my job on the kernel team at Netflix's Open Connect CDN, I do post-mortem debugging of kernel core dumps almost daily. On a fleet the size of our CDN, there will invariably be a kernel panic which you'd not otherwise be able to reproduce. In fact, I often use 2 debuggers: kgdb for most things, and occasionally a port of the OpenSolaris mdb for easily scripting exploration of the core.
My hat is off to all the people who made my debugging so much easier. Eg, the llvm/clang folks who emit DWARF, gdb folks who make the FreeBSD kgdb debugger possible, and the Solaris folks who wrote the weird and wonderful mdb.
I could show anyone of these authorities a situation where their bravado would fail a trying to figure out a particular bug by "staring at the code and thinking harder" would be impossible.
Besides - they all tend to concede that they will use print statements or whatever as a last resorted. That is just retarded - you have to add the statements, recompile the code, and eventually remove them again. Just use the goddamn debugger that's what it's there for.
What starts as a completely reasonable "hey maybe sometimes you should try to read the code and not let the debugger become a crutch" turns into a sensationalist black/white statement "I NEVER USE A DEBUGGER". Grow up.
No need to modify code, and no risk of forgetting to remove a print statement (I have seen a forgotten dump on a rare execution path leak sensitive data to users first hand). Not to mention the cases where printing can change or break the program in some cases; for example printing before sending headers in something like PHP.
Works also in C++, though not very well when the debugger stops on an exception.
Once you have extensive knowledge of how a system works, you can more easily spot incorrect code and are less likely to write it yourself. You should always treat any debugging session as "learning more about the system". If you can't explain to another person _why_ a bug occurred, then you won't be able to convince them that the problem is truly fixed.
The approach you take to learn how the system works is not important, and will vary from person to person. Experienced developers can read code, think about it, and understand what it does. Some people understand control flow through print statements. Some people can fly through with a debugger. Saying someone else's method of study is "wrong" is just silly if in the end they understand how it works.
This article is especially wrong because it makes the assumption that you must step through a program line by line when you use a debugger, which you don't have to do. You can just put breakpoints at the same places where you would have put an equivalent print statement, except now you can inspect _everything_, instead of the one or two things you bothered to print out.
See Latin cilicium, French cilice.
http://www.newadvent.org/cathen/07113b.htm
These types of article stink of this to the heavens.
1 Formulate a Hypothesis
2 Determine what data you need for verification
3 Instrument code using gdb break commands, to hook function calls, sys-calls, signals, state-changes, etc. and print debugging information (variables, stack traces, timings) to a log file.
4 Then run an automated or manual test, of the failure you are debugging.
5 Then STOP data collection. Kill the process. Shut down the debugger.
6 Perform forensics on the log file.
7 Validate/Discard the hypothesis.
With this allows you reason reliably about the computational process:
- Why did I see this log line before this one?
- Why was this variable NULL here, but not in here.
You don't need to do that while you are in debugging session. You can take your time. You can seek back and forth in the file. It's like dissecting a dead animal, not chasing a fly.
In addition, you can share concise information with your colleagues:
- This is the instrumentation
- This is the action I performed
- This is the output
And ask concise questions, that people might be able to answer:
> I expected XYZ in long line 25 to be ABC, but it was FGH. Why is this?
I'm sure I'd write more thought through code if I took a screwdriver and removed my backspace key. But that doesn't mean it's a good idea.
How about: use a debugger AND reason about the code? My pet theory: The reason the listed developers Kerningham, Torvalds et.al, don't use debuggers is because the debuggers they have available just aren't good enough. It would be very interesting to have them describe their development environments, and what debuggers they have actually used, and in which languages.
A debugger isn't much better than println if you are working in a weak type system and with a poorly integrated development environment. If you do C on linux/unix and command line gdb is your debugger then I understand why println is just as convenient.
println doesn't solve the problem of breaking in the correct location, trying a change without restarting (by moving the next statement marker to the line before the chnaged line), It doesn't support complex watch statements to filter data or visualize large data structures and so on.
A few weeks back, we got some complaints from a client who said some of their employees weren't getting allotted an appointment slot, despite the fact that the slot is supposedly free. I dived into the codebase to try and figure out the problem. There were some minor bugs which I spotted first, and could fix without using a debugger. But the appointments were still getting dropped occasionally. So I started tracing the control flow more carefully.
That’s when I found one of the strangest pieces of code I had ever seen. To figure out what the next available appointment slot was, there was a strange function which got the last occupied slot from the database as a DateTime object, converted it to a string, manipulate it using only string operations, and finally wrote it back to the database after parsing it back to a DateTime object, before returning the response! This included some timezone conversions as well! Rails has some very good support for manipulating DateTimes and timezones. And yet, the function's author had written the operation in one of the most confounding ways possible.
Now, I could have sat there and understood the function without a debugger as the article recommends. And then, having understood the function, I could have then rewritten the function using proper DateTime operations. But with a client and my mangers desperately waiting for a fix, I used a debugger to step through the code, line by line, just understanding the issue locally, and fixed the bug which was buried in one of the string operations. That solved the problem temporarily, and everyone was happy.
A week later, when I had more time, I went back and again used the debugger to do some exploratory analysis, and create a state machine model of the function, observing all the ways it was manipulating that string. I added a bunch of extra tests, and finally rewrote the function in a cleaner way.
Instead of romanticising the process of developing software by advocating the use or disuse of certain tools, we should be using every tool available to simplify our work, and achieve our tasks more efficiently.
Yep. I advocate for some "micro managing" within a team for this type of stuff (detecting things your people do that you know there are faster ways to do it). Everybody learns from that process.
I started out as a user of "print" as a debugging technique, but early on Bill Gosper took pity on me and introduced me to ITS DDT and the Lispm debugger. They both had an important property that they were always running, so when your program fails you can immediately inspect the state of open files and network connections. No automatic core dumps except for daemons. The fact that you explicitly have to attach a debugger before starting the program is a regression in Unix IMHO.
It doesn't surprise me that Linus doesn't use one as kernel debugging is its own can of worms.
Or even more generally, a "run-time code explorer", since debuggers are useful for understanding code even if you're not trying to fix a bug.
Briefly popping into the debugger can be a really quick way to find where an infinite loop is happening, especially if it's not in your code, or not where you expect. I found this especially helpful in diagnosing a FreeBSD kernel loop I ran into, once the location of the loop was clear, the fix was simple.
Not sure if this is exactly true for Unix. For embedded targets you can often attach to a running uP with GDB.
I use debuggers to track down and understand undefined behavior, where mutating the code with logging statements may cause the bug to disappear.
I use debuggers to understand the weird state third party libraries have left things in, many of which I don't have the source code to, or even headers for library-internal structures, but do have symbols for.
I use debuggers to understand and create better bug reports and workarounds for third party software crashing, when I don't have the time, the patience, or the ability (if closed source) to dive into the source code to fix it myself.
I use debuggers to verify I understand the exact cause of the crash, and to reassure myself that my "fixes" actually fixed the bug. This is especially important with once-in-a-blue-moon heisencrashes with no good repro steps. I want a stronger guarantee than "simplify and pray that fixed it".
Yes, if your buggy overcomplicated system is a constant stream of bugs, think hard, refactor, simplify, do whatever it takes to fix the system, stem the tide, and make it not a broken pile of junk.
But sometimes bugs happen to good code too though, and sneaks through all your unit tests and sanity checks anyways. And despite rumors such as:
> Linus Torvalds, the creator of Linux, does not use a debugger.
Linus absolutely uses debuggers:
>> I use gdb all the time, but I tend to use it not as a debugger, but as a disassembler on steroids that you can program.
He just pretends he's not using it as a debugger (as if "a disassembler on steroids that you can program" isn't half of what makes a debugger a debugger) and strongly encourages coding styles that don't require you to rely on them heavily.
I too, when I'm on Linux, don't use a debugger, because there's no good debugger and adding a print statement is faster and easier, and figuring out what is going on with gdb is just plain horrible and slow.
That's why as soon as I find a bug on Linux I try to have it on Windows to leverage VS's debugger. I can't count the number of times where I could instantly spot and understand a bug thanks to VS's debugger.
"Think harder about the code", sure, and what if you didn't write the code?
A couple of months ago I finally had a go at using gdb to help fix a reported crash, and found that with a debug built executable, just type "gdb progname" and then at gdb's prompt "run" - hey presto if the program crashes it gives the source line. No problem that the entry executable wasnt built with symbols. It was hugely useful for a tiny learning step.
But if you are looking for marvelous debuggers, I do recommend you look at the Python ecosystem.
Debuggers are the most amazing tools to explore and learn the code in your hands. They do have limitations, as pointed out, but presuming that your inner insight/gut feeling leads to more scalable and lasting results is ridiculous.
"In the 1950s von Neumann was employed as a consultant to IBM to review proposed and ongoing advanced technology projects. One day a week, von Neumann "held court" at 590 Madison Avenue, New York. On one of these occasions in 1954 he was confronted with the Fortran concept; John Backus remembered von Neumann being unimpressed and that he asked, "Why would you want more than machine language?" Frank Beckman, who was also present, recalled that von Neumann dismissed the whole development as "but an application of the idea of Turing's 'short code."' Donald Gilles, one of von Neumann's students at Princeton, and later a faculty member at the University of Illinois, recalled that the graduate students were being "used" to hand-assemble programs into binary for their early machine (probably the IAS machine). He took time out to build an assembler, but when von Neumann found out about it he was very angry, saying (paraphrased), "It is a waste of a valuable scientific computing instrument to use it to do clerical work.
We live in a world where every employee has a previously unimaginable amount of processing power dedicated for their personal use that spends almost all of its time idling. That results in a far different calculus than the world where processing power is a scarce resource.
Anyway, imagine a detective trying to figure out a murder scene without going through it step by step. Of-curse with experience come speed. So, weird flex but OK. I will still prefer C# over any language just because the debugger in Visual Studio is amazing!
After re-reading the article, I see it as another "Just code better" which hopefully will make it easy to pinpoint the bug just from the problem itself. In complex system with a lot of feedback loops, It's nearly impossible without debugging or using logs (which for me are just serialized debugger).
It necessarily implies some loss of control over the code you (or somebody else) wrote, i.e you're not sure anymore of what the program does - otherwise, you wouldn't be debugging it, right?
If you get into this situation, then indeed, firing up a debugger might be the fastest route to recovery (there are exceptions: e.g sometimes printf-debugging might be more appropriate, because it flattens time, and allows you to visually navigate through history and determine when thing started to went wrong).
But getting into this situation should be the exception rather than the norm. Because whatever your debugging tools are, debugging remains an unpredictable time sink (especially step-by-step debugging, cf. Peter Sommerlad "Interactive debugging is the greatest time waste" ( https://twitter.com/petersommerlad/status/107895802717532979... ) ). It's very hard to estimate how long finding and fixing a bug will take.
Using proper modularity and testing, it's indeed possible to greatly reduce the likelihood of this situation, and when it occurs, to greatly reduce the search area of a bug (e.g the crash occurs in a test that only covers 5% of the code).
I suspect, though, that most of us are dealing with legacy codebases and volatile/undocumented third-party frameworks. Which means we're in this "loss-of-control" situation from the start, and most of the time, our work consist in striving to get some control/understanding back. To make the matter worse, fully getting out of this situation might require an amount of work whose estimate would give any manager a heart attack.
It tells us more about Linus. I hope no impressionable developers take this article too seriously.
The speed of iterating changes to code has a huge influence on the tools that you use. In a language like C, compiling new changes to code can take many seconds or sometimes minutes. Thus, you'll want to have heavy duty tools that can carefully analyze how your code is operating.
In contrast, in a scripting language, changing a line and rerunning the code can take far less time (a few seconds for even large programs). Thus, you can iterate more often, and so you don't have to be as careful in each iteration.
The moral of the story is that debuggers can be extremely helpful for some languages, especially those that take a long time to compile. However, while still helpful, they are far less helpful for languages that you can run quickly (I'm thinking Python here).
Sure, maybe that guy exists. Maybe you've seen "that guy" or been "that guy". Does that mean that it has no value to be able to stop a program and look at all the values?
Is this a debugger? Sure. It sorta lets you step through the program and inspect state at various points. But it's also a REPL, and it also strongly resembles inserting print statements everywhere. It's also like an exception tracker/crash reporter and a tracing framework.
IMO it's both simpler and more powerful than any of these. It's like if every statement had a println that you never have to add but is available whenever you want to inspect it. Or like a debugger where you never have to painfully step through the program to get to the state you want.
So overall, I think we need to think deeper about what a debugger is and how it can work. Most of the people quoted do not have a good debugger available to them, nor a good debugging workflow.
“Debuggable” code is written in a certain style — just like “testable” code.
I consider a codebase to be good when there are meaningful places to put break points sufficient for running learning experiments about the code. Just like a codebase with “tests” is often a better codebase as a result of being written in a way that supports testing - a codebase that supports debugging can also often be a better codebase. And these work well together putting break points in test cases is often a really great idea!).
I think one of the reasons the value of the debugger so often fails to be noticed by experienced developers is that so many systems are architected in a horrific way which really does not allow easy debugger sessions — or the debugger platform is so underpowered that debugging is unreliable. There’s nothing worse than not trusting the debugger interface — “i want to do an experiment where I run code from here up to here” needs to be easy to describe and reliable to execute otherwise it is too much pain for the gain. In my opinion, failure to make this easy is not a fault of the concept of debugger but a fault of the codebase or the tooling (which often is very inadequate).
- Because I forgot how to use it (or never knew how.) There are many debuggers and UIs and I still know how to use some of them to decent effect, but I simply don’t know how to be effective with most of them.
- Because I’m pretty confident I have a good understanding of what code is doing nowadays. My intuition has been honed over the years and I tend to quickly guess why my code isn’t working.
- Because my code is all unit tested now. This contributes to my ability to be more sure about what code is actually doing.
There are still some cases where I may try a debugger. I had one recently where I was unsure what path my code was taking and I wasn’t sure how to printf debug. That helped a lot.
Not using a debugger is not really a choice I made or something I do to try to look impressive, rather it’s most likely a result of the growing diversity of programming languages and environments I work in, combined with better testing habits. I just feel like I have enough confidence to fix the bugs quickly. When I lose that confidence is when I break out printf or the debugger.
So does your code ever use other libraries? Does it ever call third party APIs? Do you ever have to modify code you didn’t write? Do you remember what your code does that you wrote 10 years ago?
And beyond the bugs themselves, reading the unit tests shows me what a function is expecting and what it's doing, and especially its behavior in edge cases.
The other technique I use is judiciously placed assertions that enforce invariants. I'd rather the system fail explicitly than have it returning junk data to users. (I work in finance, so customers are far more forgiving of a system returning an error message than seeing their balances be mysteriously wrong.)
Of course the first tool is careful thought. But when forced to fall back to print or log statements it feels like a handicap.
If I have to use a print statement it means I'm not sure what's going on so I'm not sure what, exactly, to print. By breaking on that code I don't have to know exactly because I can execute arbitrary code in that scope. What takes multiple iterations with print is often just one with break.
I can compare directly, because on a production-only bug I'm forced to use log debug statements. And usually in that case I have the same code open locally in a debugger. The difference is night versus day.
Maybe it's like a chess master who really doesn't need the board. For the kid in Searching for Bobby Fischer, it may really be a distraction. But I notice that grandmasters use a chess board in serious competition. And as a programmer I'm no grandmaster, and I play this weird game better with the board in front of me.
Sadly, gdb is running out of gas at Google - it takes 60s to load "hello world" + 200MB of Google middleware and often it would step into whitespace or just hang, forever. This was often because not smart people were maintaining the gdb/emacs environment at Google.
Thus I mostly agree with the author of this article --- blindly stepping through code with a debugger is not a very productive way of problem solving (I've seen it very often when I taught beginners; they'll step through code as if waiting for the debugger to say "here is the bug", completely missing the big picture and getting a sort of "tunnel-vision", tweaking code messily multiple times in order to get it to "work".) If you must use it, then make an educated guess first, mentally step through the code, and only then confirm/deny your hypothesis.
To a point, this is due to me not planning through my programs. So I can see that for some areas a debugger may not be essential.
But in other cases, I am actually interested to trace what happens with data in my mechanisms. For this kind of work, a debugger is essential.
Most importantly, as someone who maybe does not use the absolute best practices of designing software, debugging allows me to write solid and successful programs without having completed a CS degree.
The debugger is the best answer when you are working with bad code, which you cannot reason about the code locally -- a random bug only happens on the production env, a mutable monolith, or a complex system integrating with 3rd party services. Because debugging is to know what happens in the first step, then build a theory to reason about everything. Sometimes, fixing the plane in middle-air requires you need to know what happened to the specific plain, instead of building a theory to make a plane having the exact problem without looking at the plane itself. Like in control theory if there are too many possible states, it's not cost efficient to reason about the state from behavior -- wherein software you can cheat and inspect the state directly.
However, I agree with the author that in the ideal scenario there's almost no need to use the debugger. Like in SICP, in the first few chapters the mental model is about the substitution model -- it's not how a computer really works, but it's easier to reason about, you don't have to in specific step with the specific environment to reproduce the problem (that's where debugger really helps). The code is written is a matter which is more coupled with the environment (which reflects the environment model in the following chapters), the more one needs to use the debugger to work with that code. And that's why the virtue of functional programming and the referential transparency are praiseworthy.
A tier up from that for me are higher-level debuggers, generally specific to some technology. For example, the browser dev tools, GTK Inspector, wireshark, RenderDoc etc. I'd also put tools like AddressSanitizer, Valgrind and Profilers into this category. Because they are more specialized, they can give you richer information and know what information actually matters to you. I usually find I use these regularly when developing.
The highest tier is tools specialized to your specific application. This could be a custom wireshark decoder, a mock server or client, DTrace/BPFTrace scripts and probes, metrics, or even an entirely custom toolbox. Interestingly, print statements end up in this same category for me, despite being the possibly simplest tool. Being specific to the problems you actually face allows you to focus on the very specific problems you have. This tier is interesting because these tend to become not just something you use when things go wrong, but become part of how you write or run your code.
Under this lens, I don't think it's that surprising people don't really use general step debuggers that much. They are a primitive tool that allows you to have many of the benefits of tier3 debuggers without any of the effort involved in making custom tools. They are the maximum reward/effort in terms of debugging.
I am currently working on a codebase where all the developers are adamant debugger users. The code is practically impossible to debug without the use of a debugger, because no one has ever had to build up the debugging infrastructure.
Complex/diffucult bugs still take about as long to fix, but simple bugs take far longer than they normally do, because every time you use a debugger you are starting from scratch.
I want a language that exposes debugger features as a first class language construct
Think how powerful this pattern could be:
debug(...) { ... }
if the language and runtime specified the semantics needed ...
It could be really useful to have blocks like this in the codebase:
debug(problem_related_to_x) { pause_debugger; }
You could document and then more easily return to the mental context found over time when trying to understand problems related to x ...
And you could put log () statements inside those blocks instead of break points — not stupid text output but something the debugger protocol knows how to represent and present ... these capabilities alone would exceed the utility of print() debugging while supporting all the same workflows ...
I know things like vscode’s logpoints exist — but the fact that these constructs are not représentable in the code and easily shareable really undermines their overall utility ...
https://lwn.net/2000/0914/a/lt-debugger.php3
Many people say not using debugger is other side of the pendulum but perhaps it is not. You want to have assertions/prints in your program at critical junctions. That should be able to explain the behavior of the program you are seeing. If it doesn't then you probably have missed some critical junctions OR don't really understand your own code. There is actually a third possibility where you will need debugger. This is the case when compiler/programming language/standard libraries itself has bug. Instead of more time consuming binary search for where you first get unexpected output, debugger might be better option.
Oh wait; all of that is judiciously inserted print statements.
When you work mainly on enterprise code where you're unlikely to encounter any code you wrote, rather than another team member, on a daily basis you'll need a debugger or print statements.
But the reasonable point the article makes is a debugger makes it very easy to solve the problem localised to a function or a couple of lines of code rather than take the time to improve the whole area and/or add test coverage. But the other thing people who don't work on enterprise code won't necessarily understand is you don't usually have time to do that. So it's a good thing to keep in mind but it feels a little too Ivory Tower to be broadly applicable.
But sometimes I do. And when I do, I'm glad they're there, because they are a great tool of last resort.
A lot of people also have never used a debugger that isn't terrible to use. Most debuggers fall into that category.
As for all of this "read the code first and get a better understanding" talk - this is obvious highfalutin' bullshit. You're human, you made a dumb mistake somewhere, the debugger will help you find it faster than your brain going on an excursion.
I don't have anything against debuggers. I do - however - have a concern with people who rely heavily on the find feature in their IDE to search for certain code they need to change. Oftentimes they don't look at the bigger picture and miss how certain things might better be implemented elsewhere. They don't run into the problem of finding code that's poorly structured. They don't have a need to restructure it.
Of course embedded systems aren't the focus of the article, but they're all over and a very good place to have a debugger.
Do whatever works for you. But it's good to have more tools in our toolbox - use puts debugging if that's easier (often is in very complex environments), use a debugger if you need to.
Since the author quoted Guido van Rossum, I'll share a recent anecdote from my interaction with Guido at PyCon. I first met Guido this past Wednesday, I'm guessing after he attended the Python language summit. He honestly seemed to be bristly at the time, probably because he's heard many of the same arguments raised over thirty years (I can imagine something like "hey why not get rid of the GIL" -> "Wow, why didn't I think of that?! Just get rid of the GIL!" although hopefully it was more higher-level than that). One of the other language summit attendees was talking about a particular Python feature, which I don't remember, but the underlying notion was that even if you get testy with contributors they'll still be a part of the community. I remember thinking, "No. That's totally not how it works. If you get testy with contributors they'll just leave and you'll never hear from them again, or you'll turn off new contributors and behead the top of your adoption funnel meaning your language dies when you do".
Python has such great adoption because it caters to the needs of its users first. Take the `tornado` web server framework. I haven't confirmed it myself but apparently it has async in Python 2 (async is a Python 3 feature). How? By integrating exceptions into its control flow and having the user handle it. But it shipped, and it benefited its users. IMHO, `pandas` has a decent amount of feature richness in its method calls, to the point where sometimes I can't figure out what exactly calling all of them does. Why? Because C/Python interop is likely expensive for the numerical processing `pandas` does and for the traditional CPython interpreter, and ideally you want to put together the request in Python once before flushing to C, and because people need different things and so need different default args. `pandas` also ships, and benefits a lot of people.
Shipping is so important in production, because it means you matter, and you get to put food for you, your family, and the families of people you employ. You can't just bemoan debugging is bad because somebody isn't a genius or not in an architect role. Debugging means you ship, and you can hire the guy who isn't aiming for a Turing Prize who may have a crappier job otherwise and he can feed his family better.
Don't cargo cult people you're not. Use a debugger.
Also watches are invaluable when you know something is getting a wrong value but you don't know where.
Turning a blind eye to one of your tools is disingenuous. Step by step debugging has its uses and some work environments may favor it. And in fact, I'm am quite sure that guys like Linus are competent in using them and will do when needed. It is just that it is not their favorite tool and it is not well suited too their project.
I think I'll keep using them.
I tried hard to use the hardware debugger because it was rather expensive (this is I think a case of sunken costs fallacy). Problem is, our system is soft real time; stepping in the main program causes the other things connected to the system notice that the main program does not respond, and act upon this.
The hardware debugger was quite capable so we had watchpoints and scripting to avoid this problem, but you had to invest considerable amounts of time to learn to program all that correctly. Amusingly, this was another occasion to make more bugs. Now you need a debugger to debug your debugger scripts...
Moreover, the "interesting" bugs were typically those who happened very rarely (that is, on a scale of days) - bugs typically caused by subtly broken interrupt handlers; to solve that kind of bug in a decent time frame with you would need to run dozens of targets under debuggers to test various hypothesis or to collect data about the bug faster. That's not even possible sometimes.
I also happen to have developed as a hobby various interpreters. The majority were bytecode interpreters. There again debuggers were not that useful, because a generic debugger cannot really decode your bytecode. Typically you do it by hand, or if you are having real troubles, you write a "disassembler" for your bytecode and whatever debugger-like feature you need. Fortunately, the interpreters I was building all had REPLs, which naturally helps a lot with debugging.
So I'm kind of trained not to use debuggers. I learned to observe carefully the system instead, to apply logic to come up with possible causes, and to use print statements (or when it's not even possible, just LEDs) to test hypothesis.
One should keep in mind that debuggers are the last line of defense, just like unit tests that will never prove the absence of bugs. So you'd rather do whatever it takes not to have to use a debugger.
My current point of view is that the best "debugger" is a debugger built inside the program. It provides more accurate features than a generic debugger and because it is built with functions of the program, it helps with testing it too. That's a bit more work but when you do that functionality, debugging and testing support each other.
But when there is a bug in code I wrote, I can reason about it and place prints where I need them. For side projects I hardly ever use a debugger.
Well, I use debuggers. I think they're great tools. My feeling is that I'm very happy to use any tool that helps me create, understand, and improve software. When people tell me that a tool that is useful to me in that endeavor is not actually useful, all I can think to do is roll my eyes.
Having said that, something I am very interested in is learning new approaches to interrogate software complexity and solve problems. So, "here are some approaches to understanding and debugging code that have worked for me" from someone who doesn't use debuggers would be interesting to me. But I actually don't see any of that here.
I can quite easily imagine a person that only ever gets to work on problems in the latter category being dismissive of debuggers.
There is a very outspoken group that claims that if you are not using a debugger, you don't know how to write code. Appeals to authority is exactly what is required to undo the damage those people create.
I have never seen that
Debugger use is based on the same. Few things in development are not based on idiosyncratic preferences, fads, snake oil salesmen, tradition, or appeals to authority (and those are the math parts of CS). Of the 5 above categories tradition is probably the best and most "scientific" (at least it has stood the test of time).
There is little actual research on such issues (best practices, programming ergonomics, syntax leading to less errors, bug counts per style, etc), and even what little research there is usually flawed, with small samples, and non-reproducible (not that many teams bothered to reproduce it in the first place).
That's why almost nothing is ever settled.
If I ever saw a "research study" that said "print statement users have 12% fewer bugs than debugger users" - or vice versa! - I would dismiss it out of hand. What possible relevance could it have to my work? That's not real research, it's just playing with statistics and making up overly generalized stories about them.
That kind of research would just become the "authority" in a new appeal to authority, complete with an infographic!
And nothing has to be "settled", nor should it be. There are many different kinds of programming that require different tools. It's best to be aware of the variety of choices available, and choose your tools according to the particular situation you're in.
Having said that, I find your argument here pretty weird. I use debuggers because they are useful to me, not because I of quotes about them from famous programmers.
I am not going to appeal to authority, so I'll describe how I debug. 30% of my debugging is with pencil and scratch paper. 30% of my debugging is with print. And 30% of my debugging is with refactoring. The last 10% of debugging is with profiler. I do use gdb, mostly in scripting mode. I also do breakpoints manually, that goes into less than 1% and get rounded off.
I program with a meta-layer, so that I can write `$print "string interpolated with variables" in a semi-universal syntax and works for all languages that I work with. The meta layer does the parsing and translation. More importantly, the meta-layer organizes the debugging part so it can easily turn-on-off and not to clutter the actual code. Without that, I, too, cannot imagine debug with print will work.
By refactoring, I mean moving the part of code out of the way, or temporary removing or rewrite part of code that I am not sure. This is again, only possible with a meta layer that allows moving parts of code and re-organizing the code without necessarily changing the code. I cannot imaging how one can truly understand complex code without actually probing the code parts by parts.
Then I have seen the new generation of people who grow up with electronic devices and are more used to mouse than pencil. Electronic device can never match the ease and disposable nature of paper and pencil, so I cannot imagine how one could debug by merely navigating the code with an editor.
So while I do not use debugger for debugging, I can perfectly understand how one cannot live without debugger.
Appeals to authority as opposed to what alternative? Untested arguments? Anecdotal personal stories ("I am not going to appeal to authority, so I'll describe how I debug")?
(See my answer elsewhere in the thread for why almost everything is "appeal to authority" or worse in IT anyway).
We "control" our code when tests prove it does what we think it should.
Debuggers are rarely-usable, inefficient tools for software development. I agree with OP, debuggers don't scale.
> there are cases where a debugger is the right tool
Out of the 5 beliefs he quotes from celebrities we only have reasons for 3. Of those 3 the common thread I see is that we should be able to reason about our code and debuggers act to derail that. Furthermore, it appears that the aspect of debugging most being maligned here is stepping through code line by line. I'm fairly certain that is specific to a certain type of mindset. If I was writing a title for a talk in this area it might be more like "single stepping bad for students" and then talk about how to build code that is easy to model and think about and then use that to work through most problems. If you've got yourself past that student part (and yes, you'll dip back into this with new tech / languages) then being able to single step when it makes sense (don't have docs, processor isn't doing the right thing, etc.) makes you more powerful. Not less.
The focus on printing is a bit annoying. The writer seems to have never worked in embedded systems, distributed systems, or systems where reproducing the bug isn't an option. In the last case a debugger is your tool for grunging around in a core dump. In the embedded case some type of debugger or forcing a core dump (and thus using a debugger) might be your only choices.
I also question if he has ever worked on web systems. Reasoning "harder" about how some new CSS or javascript "feature" behaves across different browsers is useless. Writing little ad hoc uses (maybe in a debugger) and carefully tracking how they act in a debugger is powerful.
A lesson from my history is that of systems that take a long time to build and upload. The one I worked on early took 60 minutes to build and 30 minutes to upload to test hardware. You didn't fix bugs one by one. You fixed them by discovery, fixing on the platform (inserting nops, etc.) in assembly while replicating that in source (probably kicking of a build in case that was the last bug of this run), and then continuing to test / debug and get every little bit you could out of the session. And if you had to single step then that was worth it. Is this entirely a historical artifact? I havn't worked with anything that bad in decades but I still work with embedded (and some web) systems where the time to build and upload can be a minute to minutes. Getting more out of the session is useful and debuggers are part of that.
Refactoring as a response to a bug seems like a mistake worse than line by line stepping to me. Not understanding a cause but making a change propagates incorrect thinking about the system.
But I think the real missing part of this article is a discussion of what are other useful tools. The last comment in the article mentions "Types and tools and tests". It is easy to say tests are table stakes but a similar article about testing would create a flamefest so it is a bit hard to tell what kind of table (or is it stakes)? So what are those tools beyond testing? I'd love to have DTrace everywhere I worked. The number one best tool I've ever seen for working with a live system. The ideas in Solaris mdb about being able to build little composable tools around data structures is awesome. Immutable methods of managing databases are wonderful. It would have been nice if this author talked about design and refactoring "tools" (could be methodologies) he liked or thinks should exist.
I don't pull out the debugger very often, but knowing how and when to do that, and do it well is a significant tool in my arsenal. There are times when I can guarantee you I would have spent a massive number of hours or maybe never properly resolved certain bugs without doing it.
There’s a mental cost to every tool you learn how to use. It makes no sense to try and learn every programming tool, you’ll never get any work done. I see no reason why we should scorn people who leave “IDE” or “debugger” off their own personal list of tools that they work with. Calling it “ludditism” is name-calling, same as “dogma”.
I’ve used Visual Studio, even for extended periods of time, with its fantastic debugger. I’ve used older IDEs that weren’t as good. I’ve used various text editors and environments. What I don’t like about using a debugger is how rarely it helps more than the alternatives—so every time I need to use it, I need to learn how to use it again in whatever environment I happen to be programming in. Perhaps if you’re writing code in the same environment, the calculus is different. But no need for name calling.
Same with IDEs. Somehow, by some series of accidents, I use Emacs for about 95% of my coding. There are a couple key bindings in Emacs which I’ve set to match the default keybindings in Visual Studio or Xcode. But because I’m often programming in different environments, using Emacs instead of Visual Studio means that I can get by with learning fewer tools, and spend that effort elsewhere. No need to call it ludditism.
Just to show you how diverse our work and workflows can be, I've find the opposite of your experience. Using a debugger served me often better and faster than printlining, but using all three techniques help: print, log and interactive debug, as you need..
But as you say, I think the first premise is false. IDEs can improve productivity a lot independently of whether the code is poorly designed / maintainable etc. In fact, an IDE can help to achieve the very maintainability that is being sought.
Typed, verbose and compiled languages users tends to use an IDE by default because the amount of tooling and things to keep in mind to write executable code is higher than with dynamic, concise and interpreted languages.
People proficient with the latter categories of languages will know what is lacking in their text editor and will extend it with whatever they need (coercive linting, git integration, macros, new layout etc...) on a case to case basis. They end up with a tool that suits their strengths/weaknesses/taste, is faster than IDEs and avoid eventual problems from tools IDEs chose for you.
Chosing a text editor is not about refusing to get help from technology. It is about having a good typing experience first and around it being free to pick the tools you need.
Also, a lot of tools that both IDE and text editor users use (like git integration) do things that can be well done with good command line proficiency. So it really is about your needs
Really it is a debate similar to "Batteries-included frameworks vs multiple librairies" so there is different answers for different situations
As for IDE usage - programmers of dynamic languages keep away from IDEs, because traditionally IDEs failed to provide the same level of support as they did for static languages. E.g. autocomplete, error highlighting and refactoring were absent or at best not reliable. So if the value add over plain editors was so low, then why bother?
Having said that, I do see more and more people using IDEs with dynamic languages these days. Probably because the good ones have some limited autocomplete and error checking for dynamic languages now (e.g. PyCharm, PhpStorm).
When I was using DOS, which does not have any of these facilities, then an IDE was indeed a great choice.
At the end it depends on whether you really need assistance.
No, a computer can't assist you if you don't know what you want it to assist you with, and that is the whole premise of the main argument surrounding the anti-debugger/IDE/etc. thought, which can be summed up in one sentence: How can you tell the computer what to do, if you don't know exactly what you want it to do either?