9.4. Playing Catch-Up

Let's say that you want to go in the other direction and instead of turning errors into exceptions, you want to know when your program is about to die. The brute-force approach is to define a pseudo signal handler for __DIE__:

$SIG{__DIE__} = sub { print "Igor lives!
" };

but this is rather ineffectual for several reasons. First, we don't know why the program is dying (without parsing the argument, which is an arbitrary string). Second, we don't know where the program is dying without parsing the line number out of the argument (but when you think about it, that information is absolutely useless inside your program). And third, the Faustian bargain that this handler has made lasts only as long as it does; as soon as the handler exits, your program dies anyway.[7]

[7] Unlike real signal handlers, which return to whatever code was executing when the signal arrived.

Fortunately, there is an approach that solves two of these problems at the same time, which is to use the block form of the eval operator. Put the code you want to check on in the block, and if anything inside it causes it to die, Perl will transfer control to the end of the block and put the message from die in the special variable $@. For example,

my %dotfiles;
while (my ($username, $home) = (getpwent)[0,7])
   {
   eval
      {
      opendir HOME, $home or die "$home: $!";
      foreach (grep -f && /^./, readdir HOME)
         {
         open DOT, "$home/$_" or die "$_: $!";
         $dotfiles{$username}{$_} = () = <DOT>;
         close DOT;
         }
      closedir HOME;
      };   # Note the semicolon that is required here
   if ($@)
      {
      print "Problem with $username, reason: $@";
      }
   }

Because we get to choose how much or how little code goes inside the eval block, we can determine the granularity with which we trap exceptions. This code—which is building a hash-of-hashes containing line counts of dot files in users' home directories—uses eval to catch two possible exceptions (the failure to open a directory or a file, respectively) and then keeps going. To bail out as soon as we hit any problem, we would wrap the eval around the while loop.

A program will not exit if it dies for some reason while under the care of an eval block. However, we still have to parse $@ after the block to find out why an exception occurred, if there were multiple possible causes.

What's useful about this method of exception handling is that inside the eval block, we could be calling subroutines to any depth, be deep inside a horrible mixture of our code and someone else's libraries, and have no idea what might be going on inside them; however, if any statement in them issues a die, it will get caught by that eval block.

Bullet-proof your program by trapping exceptions and handling them gracefully.

Let's stop beating around the bush. What we really want is the ability to type exceptions and preferably also to subclass them, then catch them according to class while passing along instance-specific data.[8] Just like in Java. While we're at it, it should use the same try ... throw ... catch syntax.

[8] Not everyone thinks this way, just to be fair. If you'd rather stick with die... eval, you'll be in plenty of company.

It seems that the Perl mascot could just as well be a chameleon as a camel, since it can take on so many colors. An implementation of the try ... throw ... catch capability is in the module Error.pm by Graham Barr, on CPAN (http://search.cpan.org/search?dist=Error). It allows you to write things like

try
   {
   # Some relational database-munging code. Somewhere
   # inside this something might raise an exception with
   # a line like:
   throw Exception::SQL (-text => 'Bad SELECT statement'),
   }
catch Exception::SQL with
   {
   # Code to handle SQL exceptions
   }
catch Exception::IO with
   {
   # Code to handle I/O exceptions
   }
otherwise
   {
   # Code to handle other kinds of exceptions
   };

Exception::SQL and Exception::IO are classes of exception that we created earlier by the simple expediency of inheriting from Error itself:

@Exception::SQL::ISA = 'Error';
@Exception::IO::ISA  = 'Error';

An alternative to Error.pm is Brad Appleton's AtExit CPAN module (http://search.cpan.org/search?dist=AtExit), which allows you to specify a handler routine to be executed whenever the current scope is left for any reason, similar to the C++ auto_ptr template class. For instance:

use AtExit;
...
   {
   # Code to acquire a database connection
   atexit(&cleanup);
   # More database code which might fail
   }

This ensures that no matter how that code block is exited, the cleanup subroutine to break the connection gracefully will be run.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset