Channels ▼
RSS

Web Development

Exception Handling in Perl with Exception::Class


July, 2004: Exception Handling in Perl with Exception::Class

Dave is the coauthor of Embedding Perl in HTML with Mason (O'Reilly and Associates) and can be reached at autarch@urth.org.


Perl 5's built-in exception-handling facilities are minimal, to say the least. Basically, they consist of eval blocks (eval { ... }) and the $@ variable. If you want something a little more full featured, you'll have to turn to CPAN. Surprisingly enough, this is one area of CPAN where the number of choices is not exactly overwhelming.

The three exception-related modules on CPAN that I think are most useful and well-documented are Error.pm, Exception.pm, and my own module, Exception::Class. The first two aim at providing a more sophisticated try/catch syntax for Perl. My module, Exception::Class, is solely aimed at making it easier to declare hierarchies of exception classes. Error.pm and Exception.pm do provide base classes for exception objects, but they do not do much to help you create your own exception classes.

Declaring Exception Classes

To declare an exception class with Exception::Class, you simply use Exception::Class:

use Exception::Class ( 'My::Exception::Class' );

The above code snippet magically creates the new class My::Exception::Class. If you don't say otherwise, all exception classes created by Exception::Class are subclasses of Exception::Class::Base, which provides a basic constructor and accessors.

Of course, the above example isn't all that exciting, since you could just as easily have done it this way:

package My::Exception::Class;
use base 'Exception::Class::Base';

and gotten exactly the same result.

But what if you want to declare multiple classes at once, maybe for use within an application or suite of modules? You could type those package and use base bits over and over, or you could let Exception::Class do it for you. Here's an example adapted from the Mason code base:

use Exception::Class
( 'HTML::Mason::Exception' =>
  { description => 'generic base class for all Mason exceptions' },
  'HTML::Mason::Exception::Compiler' =>
  { isa => 'HTML::Mason::Exception',
  description => 'error thrown from the compiler' },

  'HTML::Mason::Exception::Compilation' =>
  { isa => 'HTML::Mason::Exception',
  description => "error thrown in eval of the code for a component" },

  'HTML::Mason::Exception::Compilation::IncompatibleCompiler' =>
  { isa => 'HTML::Mason::Exception::Compilation',
  description => "a component was compiled by a compiler/lexer"
           . " with incompatible options.  recompilation is needed" },
);

This is an extremely abbreviated version of the declarations found in the HTML::Mason::Exceptions module, but it demonstrates one of the core features of Exception::Class: creating a hierarchy of exception classes from a simple declaration. Note that after each class name, we pass a hash reference containing various options describing the particular class we want created.

The description parameter is a generic description of that whole class of exceptions. If your code catches an exception of a class you didn't create, adding the description to a log message may be handy. The description is not required, however.

The isa parameter is used to declare the parent of a given class. In the last example, we create a hierarchy several levels deep. The base class of our hierarchy is HTML::Mason::Exception. Below that we have HTML::Mason::Compiler and HTML::Mason::Compilation. And then finally, we have HTML::Mason::Exception::Compilation::IncompatibleCompiler, which subclasses HTML::Mason::Exception::Compilation.

How isa is Handled

Exception::Class does its best to handle whatever you throw at it in the "isa" parameter. You are not required to list your classes in any specific order, which means that that this works fine:

use Exception::Class
( 'My::Exception::Class::Subclass::Deep' =>
  { isa => ''My::Exception::Class::Subclass' },

  'My::Exception::Class::Subclass' =>
  { isa => ''My::Exception::Class' },

  'My::Exception::Class',
);

Also, if you want to use your own custom class as the base class, that also works:

package My::Exception::Class;

# no need to inherit from Exception::Class::Base if you don't want
# to, but you can.

sub new {
 ...
}

...


# in another file

use My::Exception::Class;
use Exception::Class

( 'My::Exception::Class::Subclass' =>
  { isa => 'My::Exception::Class' } );

You do need to make sure that your base class is loaded before you have Exception::Class create subclasses.

Aliases

In using exception classes, I've found that they can acquire some rather long and unwieldy names. The aforementioned HTML::Mason::Exception::Compilation::IncompatibleCompiler class name is a particularly grotesque example. Typing this more than once can overwhelm even the most iron-fingered typist, and besides that, it's incredibly easy to make a typo in the name. These typos won't be caught at compile time, so you only find out about them when that particular bit of code is executed.

Because of this, Exception::Class makes it easy to create an alias for the class, which is a function that raises the appropriate exception.

Instead of typing this:

HTML::Mason::Exception::Compilation::IncompatibleCompiler->throw
 ( error => ... );

we can type the much less finger-straining version:

wrong_compiler_error ...;

Of course, you need to tell Exception::Class to make an alias:

use Exception::Class
( ...,

  'HTML::Mason::Exception::Compilation::IncompatibleCompiler' =>
  { isa		=> 'HTML::Mason::Exception::Compilation',
  alias		=> 'wrong_compiler_error',
  description 	=>
  'a component was compiled by a compiler/lexer'
  . ' with incompatible options.  recompilation is needed' },
);

When an alias parameter is specified, Exception::Class creates a handy little subroutine in the caller:

sub wrong_compiler_error {
 HTML::Mason::Exception::Compilation::IncompatibleCompiler->throw(@_);
}

Because of the way Perl parses subroutines, if we write a call to that subroutine without parentheses, Perl will check that subroutine name at compile time. Isn't that nice?

Of course, if you define your exceptions in one package and want to use them from many others, you'll need to reexport those generated subroutines. More on this later when we look at the use of Exception::Class within an application.

Fields

You'll often find that you want to add an extra field or two to an exception. For example, let's say that you are writing a parser and, if you encounter some bad syntax, you want to throw an exception. It would be very nice to include the name of the file and line number in the file where that error occurs. This could make debugging much easier because you could display the offending line when run in debugging mode.

Exception::Class allows you to declare that a particular exception class has one or more arbitrary fields. These fields can be set when throwing an exception and will be accessible later via an accessor with the same name as the field.

So let's define our exception class with some fields:

use Exception::Class
 ( 'My::Parser::Exception::Syntax' =>
   { alias  => 'syntax_error',
   fields => [ 'filename', 'line_number' ] } );

Now when we throw the exception, we can set those fields:

My::Parser::Exception::Syntax->throw
 ( error		=> 'Expected a florble after a glorp',
   filename	=> $filename,
   line_number	=> $line_number );

And, of course, we can pass those fields to our handy alias subroutine:

syntax_error
 error		=> 'Expected a florble after a glorp',
 filename		=> $filename,
 line_number	=> $line_number;

Later, we can get those values by calling $@->filename() and $@->line_number().

Throwing and Catching Exceptions

As I mentioned earlier, if you want true try/catch syntax in Perl, you should take a look at either Error.pm or Exception.pm. In older versions of Perl, using these modules could cause memory leaks because they made it very easy to create a nested closure. Some testing I did recently seems to indicate that this is no longer a problem with Perl 5.8.4, though nested closures aren't specifically mentioned in any of the changelogs for recent versions.

If you are using an older Perl, you are probably better off avoiding these modules because creating a memory leak with them is so easy.

Of course, you can do exception handling with just eval and $@:

eval {
 open my $fh, ">$file"
   or My::Exception::System->throw
      ( error => "Cannot open $file for writing: $!" );
 ... # write something to the file
};

if ($@) {
 log_error( $@->error );
 exit;
}

But there are a couple of pitfalls in this example.

First of all, we can't assume that, just because $@ is true, that it contains an exception object. If the Perl interpreter itself threw an exception, then $@ would just be a plain string.

eval {
 open my $fh, ">$file"
   or My::Exception::System->throw
      ( error => "Cannot open $file for writing: $!" );
 print $fh 10 / $x;
 close $fh;
};

if ($@) {
 log_error( $@->error );
 exit;
}

If $x happens to be 0, then $@ will contain something like "Illegal division by zero at foo.pl line 20." If you try to call the error() method on $@ now, your program will die with a really helpful message like this:

Can't locate object method "error" via package "Illegal division by zero at foo.pl line 20."

Talk about confusing!

So you need to ensure that $@ is an object before calling methods on it. One way to do this is to set $SIG{__DIE__} to something like this:

sub make_exception_an_object {
 my $e = shift;
 ref $e && $e->can('rethrow')
   ? $e->rethrow
   : My::Exception::Generic->throw( error => $e );
}

Of course, this has several problems of its own. First, setting $SIG{__DIE__} is very, very global. Every single module you use in your code will be affected by this, which could cause some strange results. Even worse, there's no guarantee that some other code won't set it to a different value.

To be pedantic, I will point out that the test for ref $e does not actually indicate that $e is an exception object because it could very well be a plain, unblessed reference. So, if you can't cleanly force $@ to always be an exception object, you're stuck testing for this when handling it:

use Scalar::Util qw(blessed);

eval {
 open my $fh, ">$file"
   or My::Exception::System->throw
      ( error => "Cannot open $file for writing: $!" );
 print $fh 10 / $x;
 close $fh;
};

if ($@) {
 if ( blessed $@ && $@->can('error') ) {
   log_error( $@->error );
 } else {
   log_error( $@ );
 }
 exit;
}

But wait, there are more problems. We need to be very careful about using $@ after calling eval because it can mysteriously disappear. Consider, if you will, the following:

if ($@) {
 log_error($@);
 warn $@ if DEBUG;
}

It's possible that you might turn on debugging, see the value of $@ logged, and then not see it printed via warn. In fact, warn might say something like "Warning: something's wrong at foo.pl line 23." Helpful, huh?

The reason this happens is that the $@ variable is a global across all packages, and it gets reset every time Perl enters an eval block. So, if our log_error() subroutine, or for that matter any code that it calls, uses an eval block, then $@ gets reset before the call to warn on the following line.

Because of that, I strongly recommend this idiom:

eval { ... };

my $e = $@;
if ($e) { ... }

If you copy $@ into another variable right away, you can guarantee it won't be lost.

Of course, you can always encapsulate this into a nice little subroutine:

eval { ... };

handle_error();
sub handle_error {
 return unless $@;
 my $e = $@;
 ...
}

You'll notice that I've consistently tested $@ for truth rather than defined-ness. This is because $@ is set to the empty string "" when it is empty, rather than undef. Of course, if I were really diligent, I would test for length $@, in case someone does something like die 0.

Constructor Parameters

The method usually used to create a new exception is the throw() method, which constructs a new exception and then immediately calls die with that exception. You can also call new() to create a new exception if you want to do something else with it.

The constructor accepts several parameters. The most important parameter is the message you want to store in the exception. This can be passed in either the error or message parameter. As a special case, if one and only one parameter is passed to the constructor, then this is assumed to be the message parameter. This is done to make it possible to write code like this:

system_error "Cannot write to $file: $!";

If no message is given, then the $! variable is used, so you could even just write:

open my $fh, "$file" or system_error;

I'm not sure this is an idiom I'd use myself, but some may like it.

By default, when an exception object's as_string() method is called, it returns an error message without a stack trace. If you want this method's return value to include a stack trace, you can set the show_trace parameter to a true value when creating the object. If you always want objects of a certain class to include a trace, this can be controlled through a class method called Trace(), which will be covered later.

And as was shown earlier, any fields defined for the subclass being thrown can be set by passing them to the constructor:

My::Parser::Exception::Syntax->throw
 ( error		=> 'Expected a florble after a glorp',
   filename	=> $filename,
   line_number	=> $line_number );

Exception Object Methods

Once you've caught an exception object, you'll probably want to do something with it. Since Exception::Class::Base overloads stringification, you can always just treat an exception object as a string, which is handy for logging. It also means that an uncaught exception that causes a program to die will generate some sort of useful error message.

If you want to get at individual pieces of information, you can use the following methods:

  • message(), error(). Both of these methods return the error/message associated with the exception. Note that this is not the same as calling as_string(), which may include a stack trace.
  • pid(). Returns the PID at the time the exception was thrown.
  • uid(). Returns the real user ID at the time the exception was thrown.
  • gid(). Returns the real group ID at the time the exception was thrown.
  • euid(). Returns the effective user ID at the time the exception was thrown.
  • egid(). Returns the effective group ID at the time the exception was thrown.
  • time(). Returns the time in seconds since the epoch at the time the exception was thrown.
  • package(). Returns the package from which the exception was thrown.
  • file(). Returns the file within which the exception was thrown.
  • line(). Returns the line where the exception was thrown.
  • trace(). Returns the Devel::StackTrace object associated with the object. This class also overloads stringification, so you probably don't need to worry too much about the methods it offers unless you want to walk through the stack trace frame by frame.
  • description(). This method returns the description associated with the exception's class when the class was created. This can also be called as a class method.
  • as_string(). Returns the error message in string form, something like what you'd expect from die. If the class or object is set to show traces, then the full trace is also included, and the result looks a lot like calling Carp::confess().
  • full_message(). This method is called by the as_string() method to get the message for the exception object. By default, this is the same as calling the message() method but may be overridden by a subclass. An example of why this is useful is shown later.

If you want to rethrow an exception object, you can call the rethrow() method. This is basically syntactic sugar for die $exception. This does not change any of the object's attribute values. You can also call the show_trace() method with a Boolean parameter to set whether a stack trace is included when the as_string() method is called.

Class Methods

Besides the throw() and new() constructors, Exception::Class::Base also offers several class methods that let you set certain behaviors on a per-class basis. These methods make use of the Class::Data::Inheritable module. That means that a subclass inherits the value set for a parent until the method is called for the subclass, at which point the subclass's value becomes independent.

The most important of these methods is Trace(), which allows you to control whether or not a stack trace is included in the value of the as_string() method for an object of a class. If Trace() is set to a true value, then the class and its children will default to including a trace.

The other two methods, NoRefs() and RespectOverload(), also take Booleans. They default to true and false, respectively. These methods control aspects of how Devel::StackTrace stores and serializes arguments when generating a string representation of a stack trace, and the defaults are probably acceptable for most uses. See the Devel::StackTrace and Exception::Class documentation for further details.

Overriding as_string() and full_message()

The default implementation of the as_string() method does not take into account any fields that might exist for a given exception class. If you want to include these when an object is stringified or when as_string() is called, the correct way to do this is to override full_message() instead of overriding as_string().

By default, the full_message() method is equivalent to the message() method. If you recall the My::Parser::Exception::Syntax class shown earlier, we could override full_message() to include the file and line number where the syntax error occurred:

sub full_message {
 my $self = shift;
 return $self->message . ' at' . $self->filename
    . ' line ' . $self->line_number;
}

This is much easier than overriding as_string(), which you are encouraged not to do.

Using Exception::Class in an Application

If you are using Exception::Class in an application or a large suite of modules, you will probably want to declare all your exceptions in one module. Additionally, you may want to provide some utility functions related to your exceptions.

For example, you might want a function that provides an easy way of determining if you are looking at a specific subclass in your exception hierarchy. Here is an example drawn from my Alzabo module:

sub isa_alzabo_exception {
 my ($err, $name) = @_;
 return unless defined $err;

 if ($name) {
   my $class = "Alzabo::Exception::$name";
   return blessed $err && $err->isa($class);
 } else {
   return blessed $err && $err->isa('Alzabo::Exception');
 }
}

So, inside the Alzabo code, if I want to know if an exception is of a certain subclass, I can do this:

eval { .. };
my $e = $@;

if ( isa_alzabo_exception( $err, 'System' ) ) {
 ...
} elsif ( isa_alzabo_exception( $e, 'Params' ) ) {
 ...
} elsif ( isa_alzabo_exception($e) ) {
 ...
} else {
 # something else entirely
 ...
}

This function is exported by my Alzabo::Exceptions module whenever it is loaded. You may also want to reexport the functions created by Exception::Class when the alias parameter is used. This is easily done using the Exporter module:

package Alzabo::Exceptions;

use base 'Exporter';
use Exception::Class

 ( ...,

   'Alzabo::Exception::Logic' =>
   { isa => 'Alzabo::Exception',
   alias => 'logic_exception',
   },

   'Alzabo::Exception::NoSuchRow' =>
   { isa => 'Alzabo::Exception',
   alias => 'no_such_row_exception',
   },

   ...,
 );

@Alzabo::Exceptions::EXPORT_OK = ( 'logic_exception', 'no_such_row_exception' );

As long as you import these reexported aliases at compile time, you can call them without using parentheses, as was shown previously.

Integration with Error.pm

If you want to use exception classes created by Exception::Class with the try/catch syntax provided by Error.pm, you'll need to add the following hack to your code at some point:

push @Exception::Class::Base::ISA, 'Error'
  unless Exception::Class::Base->isa('Error');

It's a nasty little hack, but it works.

Integration with Exception.pm

Because of the way Exception.pm implements exception throwing and handling, I am not sure if integrating it with Exception::Class is possible. My brief experiments resulted in failure. If someone figures out how to get them to play nice together, please let me know so I can include this in the Exception::Class documentation.

Practical Applications

David Wheeler created a handy module called Exception::Class::DBI, which integrates with DBI's error handling very nicely.

If you're interested in seeing how other people are using Exception::Class, there are a number of modules on CPAN to look at. My own Alzabo module uses Exception::Class, as does HTML::Mason. David Wheeler's Bricolage, a full-fledged CMS application, uses it as well. See the Bric::Util::Fault class to start with. If you want to test code that throws exceptions, take a look at Adrian Howard's Test::Exception module.

TPJ


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 
Dr. Dobb's TV