Hacker News new | ask | show | jobs
by mettamage 1616 days ago
There are programmers that rarely need debuggers because they deeply think about the state of the program at every line of execution. I am not one of them but it is an amazing sight to see. And if they then use a debugger, they are in and out in a jiffy (usually).
2 comments

Allow me to doubt that this is possible on a regular basis.

Anyone can get bouts of error-free code. It happens on a regular basis that I write dozens of lines of code and everything just works, without any debugging.

However, it's not just the code you write, there are also bugs in code that someone wrote 5 years ago. Or a bug in a library that you have no idea about. A lot of times, these can be solved with a debugger and a few WTFs or with a week of trying to understand every line of code, it's states and the transition between those states.

Let's be realistic here.

In my experience, the hard bugs (KDE/Xfce memory leaks and use-after-free, PipeWire ID reuse race conditions between pipewire and pipewire-pulse and plasmashell and wireplumber) cannot "be solved with a debugger and a few WTFs". They occur in giant unfamiliar codebases, and solving them requires global understanding across modules (and sometimes over time, which gdb and somewhat rr are bad at). I spend days to weeks tracing code and taking notes (much as if I were learning the codebase) to actually understand it well enough to find the root cause (intended or violated assumptions and invariants, mismatches in different parts of the codebase, faulty reasoning), before I can hypothesize and implement fixes which aren't band-aid hacks.

I want a tool which takes a rr-like trace, then generates a trace of which functions call which other functions, and how work is divided between callers and callees, which loop iterations and branches are taken (which may be hundreds or thousands of pages long), then lets me subset the function calls or control flow I care about, as a starting point for me to take notes about a particular cross-cutting aspect (in the aspect-oriented programming sense, like "all calls to alsa-lib and all their call stacks" or "cross-thread shared memory accesses") or workflow (eg. startup and shutdown processes) of code execution.

I want a hybrid of architectural documentation (for implementers rather than users), personal notes, and a "debugger" for introspection/observability on code execution. I feel Pernosco aims to be the latter, and in my usage so far it's a poor choice for automating the tedium of building/codifying bird's-eye architectural understanding and global reasoning, but it might grow on me over time as a debugger for tracing data. (Functional programming promises to avoid the need for global reasoning, but I haven't looked into it.)

"Doesn't need debugger" isn't same as "error-free code."

You can solve bugs by thinking about / reading code. I'd say that's the way I solve the vast majority of my bugs.

Also "using a debugger" and "debugging" are not synonymous.

Thinking/reasoning about code is my main approach to finding and preventing bugs, and I would argue that it is by far the most important way to do so, since it is one of few ways to understand the ideas that lie behind the code as written, and to build a theory/mental model of what happens.

Still, there are cases where you will build a mental model that is wrong either due to minor bugs or larger design problems.

As a former physicist, I think of this like theoretical vs experimental physics. First you build the theory (by reviewing the code), then you run experiments (by first running the code). If you encounter surprises, you run more detailed experiements to pinpoint where your theory is wrong, and for this you can use print/log statements and/or a debugger.

In my experience, print/log statements are ok for relatively linear logic (immutable or functional patterns, for instance in a data pipeline), while the debugger may be more helpful for highly non-sequential patterns, where it is difficult to grasp what states the program may end up in (complex state machines driven by random/user input, for instance).

Then there are cases that are complicated by asynchronous, often high frequency input or that involves third party services that you cannot control (ie trading systems, logistics systems and similar systems that use a lot of concurrent transaction management, and are often connected to external systems, or even generic software like OS's, database engines, etc). Those may be impossible to reproduce properly in a debugger, and may instead need some kind of statistical approach, by bombarding the software with either live or synthetic input, and use collected metrics to build a theory that can explain the problem. (Which will trigger further experiments to validate.)

Depending on what software you are writing, using a debugger can be anything from a superpower to nearly useless.

The ability to reason about code, on the other hand, is universally useful. Arguably even for commercial software where you do not have access to the code. (As a physicist, I could not "see" quarks directly, but I could still detect them statistically by properly designed experiments.)

Scratching your left ear with your right hand doesn't mean that pelicans can't fly.

Or, in other words, you just said a bunch of seemingly-relevant keywords, but without actually making any sense. Your comment looks a lot like what GPT-3 might have to say about this discussion.

> A lot of times, these can be solved with a debugger and a few WTFs or with a week of trying to understand every line of code, it's states and the transition between those states.

In my experience the vast majority of those cases can be solved with quick search of the Github issue tracker to find someone else who has already debugged the issue, followed by an upgrade of the library to a newer version that's already been released to fix the issue, or if you're unlucky manually applying a patch.

Of course, that person's probably used a debugger to solve the issue. And sometimes you need to be that person. But IMO if you're using libraries that regular require you to pull out a debugger, then maybe you ought to be using better libraries.

That breaks as soon as the perfect code in your head has to interact with code someone else (typically and hopefully colleagues you can talk to) wrote.