
On Using Debuggers - hardik988
http://plope.com/Members/chrism/usingdebuggers
======
joblessjunkie
> "It saves me over and over again when dealing with code I don't understand."

This is the key statement of the original post.

If you don't understand the code, then you need to sit down and read it until
you do.

Using a debugger can lure you into quick, localized fixes to make small
problems disappear. If you just need to make a minor fix to an external
library that you'll never look at again, this is probably OK. But if you own
the code, you're in a rut.

Constant use of a debugger is a crutch that prevents you from deeply
understanding a problem or discovering core issues.

In the long run, a debugger slows you down. Don't step through code to verify
fixes; write automated tests. Don't set breakpoints to verify that a value is
what you expect; write assertions to confirm that they will always be so.
Don't step through new code to verify it is correct; write a unit test first
or compose your new code from smaller, proven pieces.

When confronted with a bug, sometimes the best thing to do is just stare at
the ceiling and think about what might be causing it -- quite often you'll
find the right answer.

Some bugs -- the really hard, nasty ones -- may never be revealed by stepping
with a debugger. Performance problems aren't shown by a debugger. Only
understanding your system at a high level can inspire you to make fundamental
changes in your code. A debugger blinds you to these.

~~~
ekidd
I agree with what you're saying here. However, when things start getting
seriously weird, an assembly language debugger can be invaluable.

For example, if your C compiler is generating bad code, or if you've violated
some low-level assumption, it's hard to figure out what's going on by
inspecting the source code. Sooner or later, the high-level abstractions
break, and there's nothing you can do except single-step through the code
that's going wrong.

I once found a bug in MSVC++ while compiling Quake 2. The compiler was
performing a tail-call optimization, but it hadn't noticed that Carmack had
stored a pointer to a local variable in a global variable. The stack frame was
overwritten while the variable was still live, causing weird stuff to go
wrong. This is not the kind of bug that you can quickly find by staring at C
code. :-)

~~~
tonyarkles
These are great edge cases, but certainly not the common case. I think, in
general, people jump to a debugger well before they're ready to actually
understand what the debugger is telling them. This turns into a random walk
through the source and ends up being a pretty big waste of time, compared to
actually reading and understanding what's going on.

That being said, I'm planning on using gdb this afternoon to debug an x86
(16-bit) bootloader I wrote 4 years ago. It was part of a class project, and
the prof ended up using it as example code. I think there are subtle bugs in
it though...

------
snewman
[Full disclosure: I make heavy use of both debuggers, and printf debugging, in
my work.]

A debugger gives you a detailed picture of the state of your program at one
moment in time, or (when stepping) over a short period of time. Other tools --
logging, monitoring, etc. -- provide a different slice through your program
state, making it much easier to observe the flow over time, but relatively
little detail about any given instant. Each approach has its uses.

Logging has a number of properties that make it relatively more useful for
experienced programmers, mature codebases, and/or "infrastructure" systems
(compilers, operating systems, libraries) that are used far from the
developer's workstation. That might explain its popularity among the group
interviewed in this book. For instance:

1\. Programmability. The nature of logging -- both generation and analysis --
lends itself to building up a library of utilities. An experienced programmer
develops a toolkit -- part code, part knowledge -- that makes logging more and
more useful to him/her over time. There's also a learning curve for debugging
techniques, of course, but it flattens out sooner. I've been programming for
35 years, and my logging techniques are better than they were even 5 years
ago, but my productivity in the debugger plateaued decades ago.

2\. Accumulation. If you work on a single body of code for a long time, you
can continually improve the usefulness of its logging. Eventually you reach
the point where many bugs are quickly obvious from the log, because you'd
previously invested a lot of effort in logging exactly the data you typically
need, in the most convenient format. Printf debugging can take a long time if
you're starting from scratch (the program contained no relevant logging to
start with), but in a mature codebase where attention has been paid to
logging, the story can be very different.

3\. Universality. Logging tends to work in a wide variety of environments,
from manual testing on the developer's workstation, to production servers, to
installations on a customer's computer. If you spend your life tracking down
hard-to-reproduce bugs reported by other people, logging is essential.

4\. Repetitiveness. Tracking down problems in a debugger tends to involve a
lot of boring, repetitive actions -- "aha, this is null because that other
flag had been set, let me restart and walk through the whole thing again to
see when that happens". It's still sometimes the best tool for a job, but it
has a time-sink flavor that probably pushes some people away.

~~~
BenSS
This. With hard crash/memory bugs I lean towards the debugger, but viewing the
flow over time is invaluable for more subtle logic bugs.

------
PaulHoule
A lot depends on how good your debugger is. If you're coding C# in Visual
Studio or Java in Eclipse, the debugger is dead easy to use and works well for
solving problems.

On the other hand, the only thing I use gdb for is cheating at nethack (+99
blessed Unicorn horn anyone?) and looking for buffer overflows. There are a
few PHP debuggers out there but I've yet to get one to really work right.

My main trouble with printf-debugging in all it's forms is that you end up
making changes to code that don't always go away. Even if you do it right 99%
of the time, you'll insert 1000's of printf's over the course of a month and
sooner or later one of those will end up visible to end users, will fill up
your logs with junk messages, or otherwise gunk up the works.

Of course, there's something to say with fixing bugs by looking at the code
~thinking~. Yesterday I was tracking down a bug involving an area of code that
I didn't trust, and ended up refactoring it, converting some properties in an
interface into readonly methods of an abstract base class so I knew for a fact
that certain variables wouldn't change after initialization.

This work convinced me that the code was wack, but when it was done, I knew
the problem involved initialization of the objects. For a second I thought
about using the debugger, but finding the specific case that was causing
trouble would have been tough.

At that point I decided to trace the codepath involved and immediately saw the
cause of the problem and understood the root cause (certain initialization
code was duplicated in two places, which is just asking for things to break.)

I could have fixed the bug quicker by just following the codepath, but
spending some time cleaning up code I was scared of will pay dividends in the
future... And it's a good example of the tradeoffs you face when you fix bugs.

That said, I particularly find printf-debugging useful when something strange
and difficult-to-reproduce is going on, particularly in a production system.

~~~
eru
> On the other hand, the only thing I use gdb for is cheating at nethack (+99
> blessed Unicorn horn anyone?) [...]

Might as well use wizard mode, and wish for that stuff.

I prefer playing on public servers, they take away that temptation, and give
you more bragging rights.

------
larsberg
You should also be careful about taking your opinions on debugger usage from
compiler, virtual machine runtime, and operating system implementers.
Debuggers, particularly gdb, are seldom useful for more than crash dump
analysis when you're looking at situations such as: \- Bad optimized code
being generated \- Crashed trying to do something like execute an integer
(happens with GC race conditions) \- Deadlock that only happens on maximum
number of available cores when no debugger is attached

I've had to deal with all of those specific issues in the last couple of
months on our compiler+runtime, and there's sadly not a lot of tools in the
free space to help with situations like that.

------
jeg
Queue 1000 comments from people gloating about how they too are "too good for
debuggers". I work with a number of people who don't use a debugger, primarily
because they never took the time learn how to use one. They often spend hours
and hours inserting print statements, recompiling, redeploying, etc. for
problems that can be solved in 5 minutes by using a debugger. Print statements
are fine if you're debugging your "vending machine" project for CS101, but
they don't work well for large, complex projects where you only work on a
subset of the codebase. Don't let me dissuade you, though -- you guys keep
using your print statements, and I'll be the superstar getting the same work
done in a fraction of the time.

~~~
axod
That's why you use proper logging :/

If you're making something of a reasonable size, you should be logging tons of
stuff.

If you've made sure your logging is solid, often fixing a bug is just a case
of looking in the log to see what went wrong.

~~~
jeg
I agree that proper logging can replace the need for a debugger in many
instances, but so can proper testing, talented devs, unlimited time, unlimited
budget, etc.

Under ideal circumstances (i.e. not real world), no one would ever need to
fire up a debugger, but (most of us) don't work in those conditions.

Can you create world-class software using only print statements to debug?
Sure. Is that an efficient use of your time? Probably not.

The bottom line is debuggers are just another tool in the development
aresenal, and dismissing them merely because you can accomplish the same task
in another way is asinine.

~~~
axod
I don't dismiss them because of that, I dismiss them because I can do the same
job quicker by thinking, knowing the code, being able to read and understand
any code I didn't write, and inserting a couple of print statements.

------
billjings
Here's a wild assertion about why some programmers - some good programmers -
prefer printfs: good programmers prefer to solve problems only once, rather
than repeatedly.

When you have a question you want to answer about the state of a program, a
debugger gives you powerful tools to answer your questions right now, but
doesn't leave any permanent record of your investigation. Each time you
subsequently want the answer to that same question you have to run the
program, hit that breakpoint again, and cast whatever incantations are
necessary to reveal the relevant program state.

If you used printfs instead, you would probably have had to iterate a few
times to get the answer the first time; first you print one thing, then
another, moving your prints around until they tell you what you want to know.
When you're done, though, you've probably left some log output hanging around.
So after you're done investigating, say, how the data access layer works, you
have something to cross reference while you're poking around the user
interface code.

I think this is a _more_ useful technique for unfamiliar source. Comprehending
a large hunk of source typically requires integrating your understanding of
several sections of code ranging widely across the codebase.

Anyway - a good debugger is a wonderful thing. I can't stand up straight
sometimes for leaning on them, though.

------
drblast
When I think debugger, I think gdb, which is fine to a point. But after a
while, its interactivity becomes a liability. I don't want to have to set the
same breakpoints and watches after a recompile, every time.

The integrated debuggers in IDE's are much better, particularly the Java ones.
The C/C++ ones have the problem that they will work fine on code compiled with
debug symbols, but on release code they're awful.

And when it comes down to it, the really hard to fix problems are the ones
that only show up in the optimized release code.

~~~
vilya
You can rebuild your program while you're running gdb on it. Next time you
restart the program you're debugging, it will pick up the rebuilt version and
all your breakpoints and watches will still be intact. There's even a built-in
gdb command for running make, for precisely this reason.

------
AndrewDucker
I find knowing what the stack is at the point where things go wrong incredibly
useful. Having an IDE that allows me to work my way back up the stack and see
what direction my code was called from that meant that I ended up with an
invalid state is terribly handy.

I also sometimes find it useful when calling code I don't have decent
documentation for. I can examine the various properties of the object I'm
trying to work with, and see which ones contain useful information.

------
Almaviva
What I don't read much about, but depend on, is using a debugger on new code
when there _aren't_ obvious bugs in it. Just walking through it, while viewing
variables, gives a wonderful sanity check to verify that code is doing what I
think it is doing, and this catches a surprising amount of non-obvious bugs,
and lines of code become easier to reason about precisely sometimes when you
have visibility of the specific state of the program at that time. This is not
a substitute for unit testing obviously, but it can catch problems that
wouldn't be obvious or easy to unit test for.

For me, this is also a wonderful way to get accustomed to someone else's code,
seeing exactly what it does first before spending the brain cycles upfront to
reason about exactly what is going on.

------
deathbob
The print statements to debug was a constant in "Coders at Work" too. Also in
that book I found interesting that everyone interviewed was asked what the
most difficult thing they had to debug was, and almost all of them said
something to do with concurrency.

~~~
NickPollard
That's not surprising though, if there's one thing I've had drummed into me
throughout my education and career, it's that concurrency is /hard/.
Particularly since it requires thinking about things in a completely different
way to a lot of single-threaded programming.

~~~
axod
It's also completely avoidable.

Concurrency is only hard because it's a stupid thing to do - to give a bunch
of instructions to the CPU and tell it to execute them in a random order.

~~~
eru
Why not, if you compiler proves that it's safe?

I don't even care in which order my single-threaded code gets executed (if
it's in the right language).

------
bluekeybox
I almost never use a debugger as well (used to rely on it when I was a
beginner though). Even when I'm looking at someone else's code, my time is
generally spent more productively analyzing it with pencil and paper than
running a debugger.

As some one else mentioned, I typically only use gdb for crash dump analysis.
The one tool I do use a lot though is Valgrind (to check for memory leaks).

------
davidsiems
For C/C++ the debugger is an invaluable tool.

For large projects (especially large projects that don't have the files
structured well) you can end up with very high compile and link times (> 2
minutes). Using a debugger in this case is much faster than incrementally
adding print statements to the code.

Being able to see a callstack is also incredibly valuable on complex projects.
The value of printing things out drops dramatically once you have multiple
entry points into the same codepath.

Also, if you always run your program through the debugger, when you crash you
can immediately diagnose and fix the problem without having to reproduce the
crash. This is useful for hard to reproduce bugs.

Besides, it's not like this is an 'either or' type thing. You can use a
combination of reading the code, debugging, and printing to track down
problems.

------
btilly
I had this conversation about a decade ago at
<http://www.perlmonks.org/?node_id=48495>. Unfortunately most of the links in
that discussion have gone dead. Here are fixes to the most important ones.

The Kernel Traffic summary: <http://kt.earth.li/kernel-
traffic/kt20001002_87.html> (sections 1 and 4 are relevant).

Linus' "I'm a bastard":
[http://lkml.indiana.edu/hypermail/linux/kernel/0009.0/1148.h...](http://lkml.indiana.edu/hypermail/linux/kernel/0009.0/1148.html)

Richard J Moore discussing IBM's experience:
[http://lkml.indiana.edu/hypermail/linux/kernel/0009.1/1307.h...](http://lkml.indiana.edu/hypermail/linux/kernel/0009.1/1307.html)

------
ag272
I understand this - debuggers are useful (especially when going through code
you haven't written), but if you're writing an entire program yourself, you
understand how it works completely, so a debugger is much less necessary (not
unnecessary).

I haven't fired up a debugger for a long time, partly because I'm in to
defensive programming big time. If you have something as simple as assert(ptr
!= NULL) in a function, if it fails, you get hit with an assert failed
message, the file, line, function and condition. Most of the time this results
in me thinking, "oh, duh" and immediately correcting the problem, sans
debugger.

------
auxbuss
Using a debugger on pre-production code is a waste of an opportunity to create
good tests.

A debugger can be useful when debugging production code, especially when the
heat is on. This is no time to stand on ceremony and idealism.

For learning new code, particularly frameworks ime, a step debugger is
incredibly valuable. But you are not really debugging in that case.

A debugger is a non essential tool these days. But if the kernel boys said
they found it useful, then I'd not argue.

------
edw519
What I tell others: I'm so good that I don't need a debugger.

The truth: Print statements work so well that they dramatically decrease the
need for a debugger. Then when I actually do need a debugger, I have used it
so infrequently that I forgot the commands, so I just use more Print
statements.

~~~
mesmerized
Not using a debugger is like giving up a knife and using a spoon to eat your
steak. Yeah, you can do it, but why would you? Debuggers are invaluable when
you encounter difficult bugs, especially ones related to threading issues.
Being able to freeze/run threads at will comes in handy. Setting hardware
breakpoints to see when particular memory locations are changing is also
useful. Maybe you're just not using the debugger to its full potential?

~~~
axod
Proper logging > debugger.

There is absolutely no point single stepping through code, setting up
breakpoints, inspecting variables etc, when you could instead just insert an
extra log statement to see what is going wrong.

If your logging is good, often solving a bug is just a case of looking in a
production log and seeing where things went wrong.

The other fact is that the majority of bugs I've seen in my own code have been
caused when under load, and it's not feasible to run a debugger on a
production system.

I've tried debuggers a fair amount, and they end up being completely useless
to me.

~~~
maximilianburke
Logging changes the timing of code making it nearly useless for debugging
problems of a concurrent nature. It also falls down when systems cannot afford
the overhead of printing all trace data (see: video games), or when embedded
platforms have no reasonable TTY to send log data to.

A debugger is just a tool and to dismiss a tool as being pointless for all
people is ridiculous. Being familiar with all the tools at your disposal as a
software developer is _never_ a bad thing.

~~~
axod
A great reason why you shouldn't use concurrency...

------
JoeAltmaier
Its a tool, like a yellow hammer; useful when you need it.

Since I write time-dependent C++ code I must test/debug the 'release' build.
The debuggers universally screw up almost everything related to dynamic
symbols (locals, parameters), so I have to read the assembler anyway to figure
out what's where.

Naturally this makes it much quicker to use prints (logs actually) than
struggle in the debugger.

~~~
tonyarkles
Wouldn't the print statements have the possibility of screwing up the
optimized code (changing register allocations, for example)? Or screwing up
the timing? (I realize a debugger would definitely screw up the timing)

~~~
JoeAltmaier
So I don't actually use prints; I make log entries in a system service with an
enormous buffer. It takes negligible time, certainly compared with a debugger
breakpoint!

Unless we suppose a compiler bug, reallocating registers around a print (ok,
log) is not a problem. It's still an order of magnitude faster than the
'debug' build.

------
wccrawford
I used debuggers long ago, but I've found that I work just as well, if not
better, without them. I have to actually understand the code, not just get to
a certain point and see what the variables are.

In the end, it means I know that section of code inside and out and can
rewrite and/or refactor it as needed to solve the problem. It usually results
in cleaner, more efficient code.

------
tygorius
I agree with what several have already pointed out here, that a key point is
that the famous programmers interviewed _already_ understood their code and
hence using a debugger to better understand it is not particularly useful.

I've been struck over the years at how one of key benefits of the Smalltalk
environment -- being able to explore/change things in a program while it's
running -- is not more widely regarded in the field. I occurs to me now that
the interviewed programmers might have already gotten much of the cognitive
benefit of such exploration at the beginning of a project. For them, print
statements and listings are a way of confirming details of what they expect to
be happening as the code runs. For un-godlike programmers (and maintenance
work), debugging is complicated by the lack of deep understanding of how the
code is supposed to work, hence the need for more exploration time and
debuggers.

------
Erwin
I used gdb plenty in C/C++ but that was mostly to debug mysterious errors when
exceeding array bounds. Back then continously running server software that
crashed would essentially move away its core file and restart, then just run
gdb on it and mail be the output of a stack trace and "info locals".

In Python that's sufficient to catch most logic errors -- a full stack trace
with values of all locals and member variables is enough to diagnose 90% of
errors. When the error does not lead to an exception but undesired behaviour
print debugging is easiest -- I have the software running so it restarts on
source code changes and the restart takes a second and does not lose any state
as it's a web app.

I could see myself using pdb if the Python software was a more complex server
that took longer to restart and had some complex state that'd be lost on
restarting.

------
draebek
I'm surprised to read more people arguing, "print statements and/or
understanding the code I'm working on is more valuable than a debugger" rather
than straightforward, "my platform/language/environment doesn't have a good
debugger--print statements are simply _easier_." pdb is pretty good but
wedging it into my FastCGI processes running as another user is a PITA (albeit
one I could fix just once). Clojure has CDT but that's even kind of PITA-y
since I have to go start up another JVM and get them to talk to each other.
It's much easier just to type "print x, y, z" and reload my code.

If I was in VS.Net or Eclipse and not Emacs all day, and/or if I was writing
something more "straightforward" like a desktop application, I bet I'd be much
more inclined to use a debugger.

(Edit for word choice.)

------
robgough
I couldn't imagine coding C# without a debugger (Visual Studio).

I couldn't imagine coding Ruby or PHP with a debugger.

Maybe I've just not been incentivised enough to learn how to properly debug
Ruby/PHP though.

------
Draxxus
FTA: "It saves me over and over again when dealing with code I don't
understand. Not having it would be terrible."

Perhaps that is the problem. What are you doing working with, using, or
writing code that you _don't understand_

To me this is a bit like the folks who randomly change their loop conditions
until they get the desired behavior. Sure it works correctly now, but they
have no idea _why_ their changes made it that way.

~~~
jasonkester
I think you're missing the point of the quoted statement.

A debugger is a great tool to help you understand unfamiliar code. Drop me in
to a breakpoint in somebody else's codebase, and chances are I'll quickly be
able to figure out what's going on.

Assuming you didn't write all the code in your organization, debugging is
pretty much your most valuable skill. Be it looking at a stack trace and
digging in by hand, or using the tools available to you. Your worth revolves
around being able to immediately dig in to an unfamiliar system, find our
what's wrong, and fix it.

Debuggers help you do that.

------
mcculley
My work is mostly in distributed real-time systems. All of the really hard
debugging problems (e.g., race conditions) occur in situations where you can't
pause one of the components with a debugger or you won't be able to repeat the
problem. In those cases, logging events and studying them right after
repeating the problem is your only recourse.

------
kodisha
In 98% of my time i never use them (Java/IDEA IDE).

I use profiler a lot lately though (Java Visual VM).

------
ADRIANFR
A debugger loses much of its value in a functional language. Debugging Haskell
or functional Scala code with a visual debugger is almost counterproductive.

------
TimJYoung
I can't believe that someone would use a language that didn't have an
IDE/interactive debugger. It's like going backwards in time to the early 80's.
:-)

------
mcfunley
> ... Bertrand Russell ...

wat

