The 2003 Perl Advent Calendar
[about] | [archives] | [contact] | [home]

On the 17th day of Advent my True Language brought to me..
diagnostics

Trying to work out why your code has gone wrong is one of the most time consuming tasks that consume a programmer's daily grind. It's also the most frustrating.

There's nothing worse when you're trying to solve a problem than not understanding an error message that a script is spitting out. Perl has to draw the line somewhere between explaining what the error it's printing out really means and printing out so much info that you get swamped. But sometimes you find it's drawn the line on the wrong side of the sand.

diagnostics can help explain what's going on by printing out explanations of each of the error messages as they occur in a overly verbose fashion. These explanations are taken from perldiag, and have been carefully crafted to answer the 'why is it complaining about that' question that you'll often find yourself asking.

Diagnostics can be enabled at the start of your script in the same way that you might turn warnings and strict on:

  #!/usr/bin/perl
  # turn on perl's safety features
  use strict;
  use warnings;
  # turn on extra debug info
  use diagnostics;
  # do something naughty ($foo not declared yet)
  $foo = "bar";

This prints out a fully descriptive lump of text that's explaining what's exactly going on:

  Global symbol "$foo" requires explicit package name at - line 11.
  Execution of - aborted due to compilation errors (#1)
   
      (F) You've said "use strict vars", which indicates that all
      variables must either be lexically scoped (using "my"),
      declared beforehand using "our", or explicitly qualified to
      say which package the global variable is in (using "::").

Horay! I now understand. These error messages are extracted from the handy 'what does that mean' page that is the

  • perldiag
  • manpage.

    diagnostics prints out useful information for warnings too

      #!/usr/bin/perl
      # turn on perl's safety features
      use strict;
      use warnings;
      # turn on extra debug info
      use diagnostics;
      # do something naughty ($foo undef)
      my $foo;
      my $bar = $foo . $foo;

    Which prints:

      Use of uninitialized value in concatenation (.) or string
      at - line 12 (#1)
      (W uninitialized) An undefined value was used as if it were
        already defined.  It was interpreted as a "" or a 0, but
        maybe it was a mistake. To suppress this warning assign a
        defined value to your variables.
        
        To help you figure out what was undefined, perl tells you
        what operation you used the undefined value in.  Note,
        however, that perl optimizes your program and the operation
        displayed in the warning may not necessarily appear
        literally in your program.  For example, "that $foo" is
        usually optimized into "that " . $foo, and the warning will
        refer to the concatenation (.) operator, even though there 
        is no . in your program.

    This is fine, until you get about a million of these in your program. Since warnings aren't fatal you can quickly amass screenfuls and screenfuls of text One solution is to turn warnings on for just a little bit of your code:

      #!/usr/bin/perl
      # turn on perl's safety features
      use strict;
      use warnings;
      # turn on extra debug info
      use diagnostics;
      disable diagnostics;
     
      # do something naughty ($foo undef)
      my $foo;
      my $bar = $foo . $foo;   #  no diagnostics
      enable diagnostics;
      $bar .= gmtime . "fred";  # prints out warning

    This prints out the long and helpful warning:

      Warning: Use of "gmtime" without parentheses is ambiguous
      at - line 16 (#1)
        (S ambiguous) You wrote a unary operator followed by 
        something that looks like a binary operator that could
        also have been interpreted as a term or unary operator.
        For instance, if you know that the rand function has a 
        default argument of 1.0, and you write
        
            rand + 5;
        
        you may THINK you wrote the same thing as
        
            rand() + 5;
        
        but in actual fact, you got
        
            rand(+5);
        
        So put in parentheses to say what you really mean.

    But we didn't get any diagnostics about the concatenation. Nice.

    Another possibility, one I use a lot, is have my program grind to a halt as soon as it sees a warning. This can be done by installing a signal handler that dies when it finds a problem:

      use diagnostics;
      BEGIN { $SIG{'__WARN__'} = sub { die $_[0] }; }

    splain

    splain is a little program that allows you to create diagnostics for code that's already been executed. It works by taking the error output from our code's and reprocessing it into more useful output. Let's remove diagnostics from our previous example:

      #!/usr/bin/perl
      # turn on perl's safety features
      use strict;
      use warnings;
     
      # do somethings naughty
      my $foo;
      my $bar = $foo . $foo;
      $bar .= gmtime . "fred";

    And capture the warnings to a file (This is the syntax for bash, you'll have to work out how to redirect STDERR on your system yourself.)

       bash-2.05b$ perl script 2>mystderr

    And now at any point in the future I can run splain over that output and it'll print out handy debug messages for me:

       bash-2.05b$ splain mystderr

    Which spits out the same errors as above.

  • perldiag