Perl Best Practices

18.12. Manual Debugging

Use serialized warnings when debugging "manually".

Many developers prefer not to use the debugger. Maybe they don't like the command-line interface, or the way the debugger slows down the execution of their code, or the fact that it actually changes the code it's debugging[*]. Perhaps they just dislike the tedium of stepping through a program statement by statement.

[*] The subtle changes that the debugger surreptitiously makes to any code it's executing usually pass unnoticed. However, very occasionally those manipulations can actually make debugging even more difficult, by introducing arcane phenomena like heisenbugs (errors that vanish when you try to debug them), schrödinbugs (errors that manifest only when you're trying to debug something else), and mandelbugs (complex errors that seem to fluctuate more and more chaotically, the closer you look at them).

The most popular alternative to using the debugger is to manually insert print statements at relevant points in the code. This has the distinct advantage of altering the code being debugged in limited and predictable ways.

But, if you're going to debug manually, don't use print for your print statements:

my $results = $scenario->project_outcomes( ); print "\$results: $results\n"; # debugging only

Use warn instead:

my $results = $scenario->project_outcomes( ); warn "\$results: $results";

Because warn statements will not be used anywhere else in your code (see "Reporting Failure" in Chapter 13), using them for debugging makes it very easy to subsequently find your debugging statements. Using warn also conveniently ensures that debugging messages are printed to *STDERR, rather than *STDOUT.

In addition, it's a good practice always to serialize the data structure you're reporting, using Data::Dumper:

my $results = $scenario->project_outcomes( ); use Data::Dumper qw( Dumper ); warn '$results:', Dumper($results);

By printing the value you're reporting in a structured format, you maximize the information that's subsequently available to help you debug. For example, if the project_outcomes( ) method was expected to return an Achievements object, then debugging with:

warn "\$results: $results\n";

might print:

$results: Achievements=SCALAR(0x811130)

It looks like the method is working correctly, sending back an inside-out object of class Achievements. However, adding Data::Dumper serialization to the debugging statement:

warn '$results: ', Dumper($results);

reveals a subtle problem:

$results: $VAR1 = 'Achievements=SCALAR(0x811130)'

That is, instead of returning an actual Achievements object, the call to project_outcomes( ) is returning a string instead. The expected object reference is undergoing a spurious stringification before being returned. If the method had been behaving properly, the serialized output would have indicated that there was a real object in $results:

$results: $VAR1 = bless( do{\(my $o = undef)}, 'Achievements' )

So always serialize any data structure you're debugging[*].

[*] For the same reasons, it's also a mistake to use the p (print) command in the debugger. Always use the x (examine) command instead; it serializes its output.

If you prefer this kind of manual debugging, you may find it useful to set up a macro in your editor to insert suitably serialized print statements automatically. For example, adding the following in vim:

:iab dbg use Data::Dumper qw( Dumper );^Mwarn Dumper [];^[hi

replaces any instance of 'dbg' you might insert in your program with:

use Data::Dumper qw( Dumper ); warn Dumper [_];

The macro then repositions the insertion point between the square brackets (represented by the underscore in the previous example) and allows you to continue to insert the data structure you want to debug. You can achieve a similar effect in Emacs with an entry in the global abbreviation table of your ~/.abbrev_defs file:

(define-abbrev-table 'global-abbrev-table '( ("pdbg" "use Data::Dumper qw( Dumper );\nwarn Dumper[];" nil 1) ))

Категории