Hack 38. Improve Exceptional Conditions


Die with style when something goes wrong.

Perl's exception handling is sufficiently minimal. It's easy to recover when things go wrong without having to declare every possible type of error you might possibly encounter. Yet there are times when you know you can handle certain types of exceptions, if not others. Fortunately, Perl's special exception variable $@ is more special than you might knowit can hold objects.

If you can stick an object in there, you can do just about anything.

The Hack

How would you like more context when you catch an exception? Sure, if someone uses the Carp module you can sometimes get a stack trace. That's not enough if you want to know exactly what went wrong.

For example, consider the canonical example of a system call gone awry. Try to open a file you can't touch. Good style says you should die( ) with an exception there. Robust code should catch that exceptionbut there's so much useful information that the exception string could hold, why should you have to parse the message to figure out which file it was, for example, or what the error was, or how the user tried to open the file?

Exception::Class lets you throw an exception as normal while making all of the information available through instance methods.

Suppose you've factored out all of your file opening code into a single function:

use File::Exception; sub open_file {     my ($name, $mode) = @_;     open my $fh, $mode, $name or         File::Exception->throw( file => $name, mode => $mode, error => $! );     return $fh; }

Instead of calling die( ), the function tHRow( )s a new File::Exception object, passing the file name, mode, and system error message. File::Exception subclasses Exception::Class::Base to add two more fields and a friendlier error message:

package File::Exception; use SUPER; use Exception::Class; use base 'Exception::Class::Base'; sub Fields {     my $self = shift;     return super( ), qw( file mode ); } sub file { $_[0]->{file} } sub mode { $_[0]->{mode} } sub full_message {     my $self = shift;     my $msg  = $self->message( );     my $file = $self->file( );     my $mode = $self->mode( );     return "Exception '$msg' when opening file '$file' with mode '$mode'"; } 1;

The only curious piece of the code is the Fields( ) method. Exception::Class::Base uses this to initialize the object with the proper attributes.

full_message( ) creates and returns the string used as the exception message. This is what $@ would contain if this were a normal exception. As it is, Exception::Class::Base overrides object stringification [Hack #99] so the objects appear as normal die( ) messages to users who don't realize they're objects.

Running the Hack

Call open_file( ) as usualwithin an eval( ) block:

my $fh; $fh = eval { open_file( '/dev/null', '<' ) }; warn $@ if $@; $fh = eval { open_file( '/dev', '>' ) }; warn $@ if $@;

Reading from /dev/null is okay (at least on Unix-like systems), but writing to /dev or any other directory is a problem:

Exception 'Is a directory' when opening file '/dev' with mode '>'     at directory_whacker.pl line 10.

The real power comes when you treat the object as an object:

$fh = eval { open_file( '/dev', '>' ) }; if (my $error = $@) {     warn sprintf "Tried to open %s '%s' as user %s at %s: %s\\n",         $error->mode( ), $error->file( ), $error->uid( ),         scalar( localtime( $error->time( ) ) ),         $error->error( ); }

What are the other methods? They're methods available on all Exception::Class objects.

Make a copy of $@ as soon as possible, lest another eval( ) block somewhere overwrite your object out from underneath you.


Now instead of having to parse the string for potentially useful information, you can debug and, if possible, recover with better debugging information:

Tried to open > '/dev' as user 1000 at Tue Jan 17 21:58:00 2006:     Is a directory

Hacking the Hack

Exception::Class objects are objectsso they can have relationships with each other. You can subclass them and make an entire hierarchy of exceptions, if your application needs them. You can also catch and redispatch them based on their type or any other characteristic you want.

Best of all, if someone doesn't want to care that you're throwing objects, she doesn't have to. They still behave just like normal exceptions.



Perl Hacks
Perl Hacks: Tips & Tools for Programming, Debugging, and Surviving
ISBN: 0596526741
EAN: 2147483647
Year: 2004
Pages: 141

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net