http://qs321.pair.com?node_id=466310

sgifford has asked for the wisdom of the Perl Monks concerning the following question:

As seems to happen to all Perl programmers eventually, I've been thinking about error handling in modules I code. It seems like I always start off just returning undef or using die, then after a while want to know more information about why something failed, and end up writing some kind of half-assed error handling system, which I gradually expand. And this seems to be what everybody else does, so you have to wade through documentation to know whether a module has custom error handling code, uses variables like $! or $@, or just dies. I usually end up just writing things like:
my $obj = Some::Object->new() or die "Couldn't create Some::object - $!\n";
and plan on fixing it later, which only occasionally happens.

die and eval solve part of this problem, but it's annoying to have to wrap every constructor and method call inside of an eval block, and in general die should only be used for truly unexpected and exceptional conditions, not simple errors that may happen from time to time.

Realizing I was about to start coding another half-assed error handling system, I decided instead to try to put together something more flexible. My goals are:

I've posted the code for my fellow monks' consideration and comments. I used it for a module called Ekahau, so it's called Ekahau::ErrHandler; of course it would be renamed if I uploaded it to CPAN.

Interface for Module Programmers

To make this as simple as possible for module programmers, there are only 5 simple requirements:
  1. Inherit from Ekahau::ErrHandler
  2. Implement an ERROBJ method which returns the error object.
  3. Create an error handler object in the constructor.
  4. Inform the error handling module when the object is constructed, so it can store error methods in the object instead of in a class-wide variable.
  5. Indicate error with return $self->reterr("error message"), which will set the error message then return undef.
For example, here's a typical usage:
package Some::Object; use base 'Ekahau::ErrHandler'; sub new { my $class = shift; my(%p) = @_; my $self = {}; bless $self,$class; $self->{_errhandler} = Ekahau::ErrHandler->errhandler_new($class,%p); return $self->reterr("An error happened") if ($error_happened); $self->errhandler_constructed(); } sub ERROBJ { my $self = shift; $self->{_errhandler}; }

User Interface

If an error is indicated by a constructor or method returning undef, the last error can be retreived with the lasterr method. If the error happened during a method call, you can get the last error for that object with $obj->lasterr; if the constructor failed, you can use Class->lasterr to get the last constructor error.

That means you can do:

my $obj = Some::Object->new() or die "Couldn't create Some::Object - ".Some::Object->lasterr; $obj->method_call() or die "Couldn't method_call $obj - ".$obj->lasterr;

Customizing

You can customize the error handling at three levels: for a particular object, for a specific class, and for all classes which use Ekahau::ErrHandler. There are two types of customization currently supported.

First, you can use the set_errholder method or the ErrorHolder constructor argument to give a reference to a scalar where errors can be stored. This can make those die statements more readable, but more importantly can be used for threadsafe error handling, since it avoids using a global variable shared across all threads. For example:

my $err; my $obj = Some::Object->new(ErrorHolder => \$err) or die "Couldn't create Some::Object - $err\n"; $obj->method_call or die "Couldn't method_call $obj - $err\n";

Second, you can use set_errhandler to set your own error handler that should be called when a module encounters an error. For example, if you prefer to use eval/die for your error handling, you could use:

Ekahau::ErrHandler->set_errhandler(sub { die @_ } );
to get that effect for all modules using Ekahau::ErrHandler.

Replies are listed 'Best First'.
Re: Thinking about module error handling
by Tanktalus (Canon) on Jun 13, 2005 at 22:46 UTC

    Stages of error handling:

    1. Hey, this is easy - just grab the return code, and use it...
      $rc = func(); if ($rc != 0) { die "Failed to func!"; }
    2. Oh, neat! Exceptions!
      eval { func(); } if ($@) { die "Failed to func: $@"; }
    3. Exceptions/OO are da bomb! Look at this!
      use Error::Handler; $rc = func(); if (not $rc->ok()) { die $rc->error_msg(); }
    4. Damn, that's way too much work...
      $rc = func(); if ($rc != 0) { die "Failed to func!"; }

    At least, that's my experience...

      That's similar to my experience, which is why this module is API compatible with (1) and (4). On top of that simple and ubiquitous interface, it adds a way to find out more details about the error if the application programmer wants it---like $! but more general.