Are the "Two Ps" (print statements and prayer) all that comprise your debugging strategy? It's time you were introduced to the Interactive Ruby Debugger.
Commands to Control Execution
Once you’ve hit a breakpoint and surveyed the state of variables and the call stack, you’ll likely want to continue marching through program execution. The commands step and next both advance the program one statement, but if the statement is a method call, next executes the entire method without stopping.
You can add breakpoints on an ad hoc basis with break. There are two forms; use help break if you ever need a refresher.
(rdb:43) help break
b[reak] file:line [if expr]
b[reak] class(.|#)method [if expr]
set breakpoint to some position, (optionally) if expr == true
finish is another useful command. It continues execution through the end of the current frame and pauses. This is especially useful to unwind back through the call stack. continue resumes execution.
Convenience Commands
Two of the most helpful commands in rdb are edit and tmate. (tmate is available only in Mac OS X versions of Rails.) Type either command to edit the code of the current frame in your default editor ($VISUAL or $EDITOR) or in TextMate, respectively. If you find a bug, now you can squish it immediately.
You can also customize your ruby-debug session with a number of settings. To see the list of options, type help set.
(rdb:43) help break
Modifies parts of the ruby-debug environment. Boolean values take
on, off, 1 or 0.
You can see these environment settings with the "show" command.
--
List of set subcommands:
--
set annotate -- Set annotation level
set args -- Set argument list to give program being debugged when it is started
set autoeval -- Evaluate every unrecognized command
set autolist -- Execute 'list' command on every breakpoint
set autoirb -- Invoke IRB on every stop
set autoreload -- Reload source code when changed
set basename -- Report file basename only showing file names
set callstyle -- Set how you want call parameters displayed
set debuggertesting -- Used when testing the debugger
set forcestep -- Make sure 'next/step' commands always move to a new line
set fullpath -- Display full file names in frames
set history -- Generic command for setting command history parameters
set keep-frame-bindings -- Save frame binding on each call
set linetrace+ -- Set line execution tracing to show different lines
set linetrace -- Set line execution tracing
set listsize -- Set number of source lines to list by default
set trace -- Display stack trace when 'eval' raises exception
set width -- Number of characters the debugger thinks are in a line
Useful options include listsize to expand the output of list, history to save debugger commands across sessions, and basename and fullpath to toggle long file name in stack traces.
Work Smarter, Not Harder
Modern programming languages and integrated development environments typically provide the equivalent of what’s shown here for Rails. A debugger has real advantages over dumping text:
- You can peek into your code and make ad hoc inspections.
- Your code is unaffected by transient changes.
- You can react to conditions and results to force different paths through your code.
And you need not wait for a bug to use the debugger. If you want to learn how something works, step through the code in ruby-debug.
The debugger is your friend.
Comments on "Hey, Don’t Dump. Debug!"
As a user and advocate of trace prints for debugging, here is why I think they remain a popular strategy. When you learn to program in a language, one of the first things you learn is how to get data out of the program. So, it’s a tool that’s always in the forefront of your mind when programming and debugging.
On the other hand, debuggers tend to be specific to a single OS and/or language, with a large set of arcane and cryptic commands that you need to learn on top of the language. And moving from one debugger to another is often more mind-warping than moving from one language to another. In the younger days of Unix, I had gotten to know a few tricks in db and adb, only to find that knowledge of no use in gdb on Linux. So, I’ve learned how to get a backtrace in gdb, only so I can more precisely target my trace prints by narrowing down the point of failure. I have yet to learn much more than that in gdb or any other recent debugger.