← Back to context

Comment by mumblemumble

4 years ago

There's a part of me that wants to say that that opinion has to be taken with a grain of salt and a lump of paying attention to who is offering it.

Circa 1999, one would assume that Brian Kernighan and Rob Pike are largely drawing experience from working with C, which is a relatively verbose language. Single stepping through C code in a debugger is indeed a laborious process.

If you read accounts from Smalltalk developers, on the other hand, it's clear that they very nearly live their entire lives inside the debugger, and consider it to be an enormous productivity booster.

I would guess that there are several effects going on there. One would be that, in terms of how much effective work it accomplishes, a line of Smalltalk code is generally not equivalent to a line of C code. That has a big impact on just how many steps are involved in single-stepping your way through a region. The other is the nature of the debugger itself. Gdb and Smalltalk debuggers are wildly different pieces of software.

Linus used to be against kernel debugging for the longest time.

The core of his position (as I understand it) was that regularly needing a debugger is a sign that your software has "gotten away from you". You've let the software get to a state where it cannot easily be understood from the architecture and program text alone.

I do think debuggers can be useful when building up comprehension - particularly of other people's software.

It's very time consuming though, and at each "trace" you're only seeing one path through the program. Good architecture and documentation lets you understand all the possible paths at once.

  • I hold the very same opinion, interactive debugging in general should be a rare need.

    If it's being used too often then it points to the fact that the software has to be run in order to understand it. It's representation is not sufficient to convey it's run time behaviour.

    Also would like to point that dynamic languages in general require more debugging than statically typed one's since one can't be certain of the data flow within functions.

    • I usually find that debuggers are more useful in dynamic languages. As an example in python many bugs are due to an incorrect structure being passed into a method due to the method or input being underspecified/abused.

      In such a case it's difficult to decide what to print or more particularly how to print it if it's type/shape is unknown.

    • I find that it's not specifically the kind of programming language, so much as the way the data is structured. Passing around big generic heterogeneous data structures makes it harder to just reason about state. That's a lot more common in dynamic languages, but I also see it happen plenty in, e.g., enterprise Java code.

      In those situations, it's often just so much easier to set a breakpoint and take a peek than it is to waste brain cycles on thinking about what exactly you should even be printing in the first place.

  • I’ve felt the same way about IDEs in general.

    We need more tooling to help people understand and mitigate necessary complexity, not tools that help one muddle through or — I shudder to think — extend complexity.

    I’ve changed my mind on this recently only because some IDEs have indeed become good at the latter.

  • Once you traipse into concurrency land, too, debuggers get much more tricky. You now have to consider the state of the thread you are in, plus all the others.

  • Agreed. The only time I've found a debugger useful is when the print statements aren't immediately giving clarity, and cognitive dissonance is settling in.

    • I think its the same as cognitive dissonance but I always find debuggers help when my code appears to be doing something impossible- like I’ll be thinking how could if(true) ever fail then remember oh this whole function isn’t even getting called and I had fixated on the wrong place. Debuggers will sort it out faster than a bunch of print statements.

I guess you could view it that way although I prefer to think of what Smalltalk developers do as living inside a REPL. And that is indeed something I can relate to. I program Julia and I basically stay in a REPL all day. But I don't regard that as the same as using a debugger. Like a Smalltalk developer, I evaluate specific functions/methods with particular values. I don't step through code. I am pretty sure Smalltalk developers don't step through code a lot.

Rather they do live changes of their code, and then evaluate various objects to verify that things work as expected. That how I seem to remember working in Smalltalk many years ago.

I am not a fan of debuggers, although I do use the REPL a lot. I suppose like Rob Pike, I used them only for very limited tasks, such as getting a stack trace or getting some sense of control flow. But as soon as I have that, I spend more time looking at code and reasoning about it, than stepping in a debugger. With a REPL I can try out assumptions I make about the code, rather than being forced to step through it.

Perhaps it has to do with dynamism? I find myself "debugging" way more in dynamic languages that emphasize interactive development. At that point, the line between running code and debugging gets pretty blurry, since "stopping" execution at some arbitrary place is not very different from normal development anyway.

  • I think this is a really good split in methodology to identify. I've noticed the same in the way I debug static vs dynamic languages. It seems to reflect the nature of the language; dynamic languages are powerful because they are fuzzy, but that comes at the cost of comprehension, and static languages tend to be the opposite.

It also matters which debugger you are using and which features it has. I personally find gdb to be completely awful, while some IDE debuggers are a delightful. With Java, you can hotswap code into a running program. And you could write the majority of your program like that. It becomes an interactive experience a little bit similar to using a jupyter notebook.

You can also make a weird sort of UI this way, where the way you interact with your program is by changing the code / and or state while it's running. Breakpoints prompt for input.