I can't help but feel this is an opinion only someone who has only ever worked on extremely high level (and basic) systems and has simultaneously disregarded all underlying abstractions could hold, or someone who has only worked with very low level systems which were so shallow my aforementioned facetious approach to debugging is actually feasible. Limiting yourself to either extreme doesn't exactly give one the most balanced view of how problems can be solved in software
"you can't explain what's going on even though you understand the code"
If someone's working on code what's wrong with using the debugger to understand the code better? There's all sorts of behavior defined underneath code that people write that can be much simpler to understand through analysis of the system in action than through code, especially when you don't have access to what's going on underneath your code due to abstractions.
There are a lot of 1+1=3 type situations that arise from abstractions people don't have access to the source to, or the resources to analyze at a given moment.
Uh huh, because you've read the source code for your program, including all the bit someone else wrote, plus the database code, and the code for your desktop manager, and the OS code for good measure. And since it's all "open" (like say, DNA), it's clear how it runs.
As a counter claim: In my experience, programmers that don't use debuggers, are those that have difficulties understanding things. They just throw code at it until something sticks.
To understand what's wrong with my program, I need a strong understanding of the data it manipulates. Show me the data, and I can probably track down the bug. The sequence of operations shown by step-by-step debugging is important, but never helped me as much. I have invariants in mind, and I can detect invariant violations by looking at the data, not at the operations between them.
In practice it means I use Valgrind first to weed out most undefined behaviours, then printf(). Yep, printf().
Take my VM for example. Had many bugs, many of them hard to track: off-by-one errors were not detectable by Valgrind for instance, because my VM heap was a giant std::vector<Word>. The GC was wrong, the stack management was wrong, the primitives were wrong… I made errors pretty much everywhere. What saved me was a little printf() based visualization tool. I could now see every block in my heap, and if anything went wrong there, I would detect it very quickly. This became my new Valgrind.
I have no idea how gdb would have helped me there. Didn't need it anyway.
Either is fine. Most conversations about debuggers however tend to equate the debugger with something like gdb or the IDE debugging interface. People will often say the absence of such a debugger is a deal breaker for them. It feels like they forgot about printf.
When I know that memory corruption is occurring (I work in embedded C++), a debugger with watchpoints is about the only way to track down who has that stray pointer.
Sure, a debugger isn't an excuse for not thinking about what you're doing, but banning them outright is a ridiculous idea.