Failure is a uni­ver­sal truth of com­put­ers. Files fail to open, web pages fail to load, pro­grams fail to install, mes­sages fail to arrive. As a devel­op­er you have no choice but to work in a seem­ing­ly hos­tile envi­ron­ment in which bugs and errors lurk around every corner.

Hopefully you find and fix the bugs dur­ing devel­op­ment and test­ing, but even with all bugs squashed excep­tion­al con­di­tions can occur. It’s your job as a Perl devel­op­er to use the tools avail­able to you to han­dle these excep­tions. Here are a few of them.

eval, die and $EVAL_ERROR ([email protected]) (updated)

Perl has a prim­i­tive but effec­tive mech­a­nism for run­ning code that may fail called eval. It runs either a string or block of Perl code, trap­ping any errors so that the enclos­ing pro­gram does­n’t crash. It’s your job then to ignore or han­dle the error; eval will return undef (or an emp­ty list in list con­text) and set the mag­ic vari­able [email protected] to the error string. (You can spell that $EVAL_ERROR if you use the English mod­ule, which you prob­a­bly should to allow for more read­able code.) Here’s a con­trived example:

use English;

eval { $foo / 0; 1 }
  or warn "tried to divide by zero: $EVAL_ERROR";

(Why the 1 at the end of the block? It forces the eval to return true if it suc­ceeds; the or con­di­tion is exe­cut­ed if it returns false.)

What if you want to pur­pose­ful­ly cause an excep­tion, so that an enclos­ing eval (pos­si­bly sev­er­al lay­ers up) can han­dle it? You use die:

use English;

eval { process_file('foo.txt'); 1 }
  or warn "couldn't process file: $EVAL_ERROR";

sub process_file {
    my $file = shift;
    open my $fh, '<', $file
      or die "couldn't read $file: $OS_ERROR";

    ... # do something with $fh
}

It’s worth repeat­ing that as a state­ment: You use excep­tions so that enclos­ing code can decide how to han­dle the error. Contrast this with sim­ply han­dling a func­tion’s return val­ue at the time it’s exe­cut­ed: except in the sim­plest of scripts, that part of the code like­ly has no idea what the error means to the rest of the appli­ca­tion or how to best han­dle the problem.

autodie

Since many of Perl’s built-​in func­tions (like open) return false or oth­er val­ues on fail­ure, it can be tedious and error-​prone to make sure that all of them report prob­lems as excep­tions. Enter autodie, which will help­ful­ly replace the func­tions you choose with equiv­a­lents that throw excep­tions. Introduced in Perl 5.10.1, it only affects the enclos­ing code block, and even goes so far as to set $EVAL_ERROR to an object that can be queried for more detail. Here’s an example:

use English;
use autodie; # defaults to everything but system and exec

eval { open my $fh, '<', 'foo.txt'; 1 } or do {
    if ($EVAL_ERROR
      and $EVAL_ERROR->isa('autodie::exception') {
        warn 'Error from open'
          if $EVAL_ERROR->matches('open');
        warn 'I/O error'
          if $EVAL_ERROR->matches(':io');
    }
    elsif ($EVAL_ERROR) {
        warn "Something else went wrong: $EVAL_ERROR";
    }
};

try and catch

If you’re famil­iar with oth­er pro­gram­ming lan­guages, you’re prob­a­bly look­ing for syn­tax like try and catch for your excep­tion needs. The good news is that it’s com­ing in Perl 5.34 thanks to the ever-​productive Paul LeoNerd” Evans; the bet­ter news is that you can use it today with his Feature::Compat::Try mod­ule, itself a dis­til­la­tion of his pop­u­lar Syntax::Keyword::Try. Here’s an example:

use English;
use autodie;
use Feature::Compat::Try;

sub foo {
    try {
        attempt_a_thing();
        return 'success!';
    }
    catch ($exception) {
        return "failure: $exception"
          if not $exception->isa('autodie::exception');

        return 'failed in ' . $exception->function
          . ' line '        . $exception->line
          . ' called with '
          . join ', ', @{$exception->args};
    }
}

Note that autodie and Feature::Compat::Try are com­ple­men­tary and can be used togeth­er; also note that unlike an eval block, you can return from the enclos­ing func­tion in a try block.

The under­ly­ing Syntax::Keyword::Try mod­ule has even more options like a finally block and a cou­ple exper­i­men­tal fea­tures. I now pre­fer it to oth­er mod­ules that imple­ment try/​catch syn­tax like Try::Tiny and TryCatch (even though we use Try::Tiny at work). If all you need is the basic syn­tax above, using Feature::Compat::Try will get you used to the seman­tics that are com­ing in the next ver­sion of Perl.

Other exception modules (updated)

autodie is nice, and some oth­er mod­ules and frame­works imple­ment their own excep­tion class­es, but what if you want some help defin­ing your own? After all, an error string can only con­vey so much infor­ma­tion, may be dif­fi­cult to parse, and may need to change as busi­ness require­ments change.

Although CPAN has the pop­u­lar Exception::Class mod­ule, its author Dave Rolsky rec­om­mends that you use Throwable if you’re using Moose or Moo. If you’re rolling your own objects, use Throwable::Error.

Using Throwable could­n’t be simpler:

package Foo;

use Moo;
with 'Throwable';

has message => (is => 'ro');

... # later...

package main; 
Foo->throw( {message => 'something went wrong'} );

And it comes with Throwable::Error, which you can sub­class to get sev­er­al use­ful methods:

package Local::My::Error;
use parent 'Throwable::Error';

... # later...

package main;
use Feature::Compat::Try;

try {
    Local::My::Error->throw('something bad');
}
catch ($exception) {
    warn $exception->stack_trace->as_string;
}

(That stack_trace attribute comes cour­tesy of the StackTrace::Auto role com­posed into Throwable::Error. Moo and Moose users should sim­ply com­pose it into their class­es to get it.)

Testing exceptions with Test::Exception

Inevitably bugs will creep in to your code, and auto­mat­ed tests are one of the main weapons in a devel­op­er’s arse­nal against them. Use Test::Exception when writ­ing tests against code that emits excep­tions to see whether it behaves as expected:

use English;
use Test::More;
use Test::Exception;

...

throws_ok(sub { $foo->method(42) }, qr/error 42/,
  'method throws an error when it gets 42');
throws_ok(sub { $foo->method(57) }, 'My::Exception::Class',
  'method throws the right exception class');

dies_ok(sub { $bar->method() }, 'method died, no params');

lives_and(sub { is($baz->method(17), 17) },
  'method ran without exception, returned right value'); 

throws_ok(sub { $qux->process('nonexistent_file.txt') },
  'autodie::exception', # hey look, it's autodie again
  'got an autodie exception',
);
my $exception = $EVAL_ERROR;
SKIP: {
    skip 'no autodie exception thrown', 1
      unless $exception
      and $exception->isa('autodie::exception');
    ok($exception->match(':socket'),
      'was a socket error:' . $exception->errno);
}

done_testing();

Note that Test::Exceptions func­tions don’t mess with $EVAL_ERROR, so you’re free to check its val­ue right after you call it.

Documenting errors and exceptions

If I can leave you with one mes­sage, it’s this: Please doc­u­ment every error and excep­tion your code pro­duces, prefer­ably in a place and lan­guage that the end-​user can under­stand. The DIAGNOSTICS sec­tion of your doc­u­men­ta­tion (you are writ­ing doc­u­men­ta­tion, right, not just code com­ments?) is a great can­di­date. You can mod­el this sec­tion after the perldiag man­u­al page, which goes into great detail about many of the error mes­sages gen­er­at­ed by Perl itself.

(A pre­vi­ous ver­sion of this arti­cle did not note that one should make sure a suc­cess­ful eval returns true, and incor­rect­ly stat­ed that Class::Exception and Throwable were dep­re­cat­ed due to a bug in the MetaCPAN web site. Thanks to Dan Book for the corrections.)