Exceptional Perl: Failure is an option
Tags: programming, perl, testing, documentation
Published: 2021-02-23 01:11:23

---
title: Exceptional Perl: Failure is an option
published: true
date: 2021-02-23 01:07:30 UTC
tags: Programming,Perl,testing,documentation
canonical_url: https://phoenixtrap.com/2021/02/22/exceptional-perl-failure-is-an-option/
---

Failure is a universal truth of computers. Files fail to open, web pages fail to load, programs fail to install, messages fail to arrive. As a developer you have no choice but to work in a seemingly hostile environment in which bugs and errors lurk around every corner.

Hopefully you find and fix the bugs during development and testing, but even with all bugs squashed exceptional conditions can occur. It’s your job as a Perl developer to use the tools available to you to handle these exceptions. Here are a few of them.

## [`eval`](https://perldoc.pl/functions/eval), [`die`](https://perldoc.pl/functions/die) and [`$EVAL_ERROR`](https://perldoc.pl/variables/$@) [`($@)`](https://perldoc.pl/variables/$@) _(updated)_

Perl has a primitive but effective mechanism for running code that may fail called [`eval`](https://perldoc.pl/functions/eval). It runs either a string or block of Perl code, trapping any errors so that the enclosing program doesn’t crash. It’s your job then to ignore or handle the error; `eval` will return `undef` (or an empty list in list [context](https://perldoc.pl/perldata#Context)) and set the magic variable `$@` to the error string. (You can spell that `$EVAL_ERROR` if you `use` the [`English`](https://perldoc.pl/English) module, which **you probably should to allow for more readable code**.) Here’s a contrived example:

```perl
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 succeeds; the `or` condition is executed if it returns false.)

What if you want to purposefully cause an exception, so that an enclosing eval (possibly several layers up) can handle it? You use [`die`](https://perldoc.pl/functions/die):

```perl
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 repeating that as a statement: **You use exceptions so that enclosing code can decide how to handle the error.** Contrast this with simply handling a function’s return value at the time it’s executed: except in the simplest of scripts, that part of the code likely has no idea what the error means to the rest of the application or how to best handle the problem.

## [`autodie`](https://perldoc.pl/autodie) _(updated)_

Since many of Perl’s built-in functions (like [`open`](https://perldoc.pl/functions/open)) return false or other values on failure, it can be tedious and error-prone to make sure that all of them report problems as exceptions. Enter **[`autodie`](https://perldoc.pl/autodie), which will helpfully replace the functions [you choose](https://perldoc.pl/autodie#CATEGORIES) with equivalents that throw exceptions**. [Introduced in Perl 5.10.1](https://perldoc.pl/perl5101delta#autodie), it only affects the enclosing code block, and even goes so far as to set `$EVAL_ERROR` to [an object](https://perldoc.pl/autodie::exception) that can be queried for more detail. Here’s an example:

```perl
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 familiar with other programming languages, you’re probably looking for syntax like `try` and `catch` for your exception needs. **The good news is that it’s [coming in Perl 5.34](https://github.com/Perl/perl5/issues/18504)** thanks to the ever-productive [Paul “LeoNerd” Evans](https://leonerds-code.blogspot.com/); the better news is that you can use it today with his [`Feature::Compat::Try`](https://metacpan.org/pod/Feature::Compat::Try) module, itself a distillation of his popular [`Syntax::Keyword::Try`](https://metacpan.org/pod/Syntax::Keyword::Try). Here’s an example:

```perl
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 complementary and can be used together; also note that unlike an `eval` block, you can return from the enclosing function in a `try` block.

The underlying `Syntax::Keyword::Try` module has even more options like a [`finally`](https://metacpan.org/pod/Syntax::Keyword::Try#finally) block and a couple [experimental features](https://metacpan.org/pod/Syntax::Keyword::Try#Experimental-Features). I now prefer it to [other modules](https://metacpan.org/pod/Syntax::Keyword::Try#OTHER-MODULES) that implement `try`/`catch` syntax like [`Try::Tiny`](https://metacpan.org/pod/Try::Tiny) and [`TryCatch`](https://metacpan.org/pod/TryCatch) (even though we use `Try::Tiny` at work). If all you need is the basic syntax above, **using `Feature::Compat::Try` will get you used to the semantics that are coming** in the next version of Perl.

## Other exception modules _(updated)_

`autodie` is nice, and some [other](https://metacpan.org/pod/HTTP::Exception) [modules](https://metacpan.org/pod/HTTP::Throwable) [and](https://metacpan.org/pod/Mojo::Exception) [frameworks](https://metacpan.org/pod/Dancer::Exception) implement their own exception classes, but what if you want some help defining your own? After all, an error string can only convey so much information, may be difficult to parse, and may need to change as business requirements change.

Although CPAN has the popular [`Exception::Class`](https://metacpan.org/pod/Exception::Class) module, its author Dave Rolsky recommends that you **use [`Throwable`](https://metacpan.org/pod/Throwable) if you're using [`Moose`](https://metacpan.org/pod/Moose) or [`Moo`](https://metacpan.org/pod/Moo)**. If you’re [rolling your own objects](https://perldoc.pl/perlobj), **use [`Throwable::Error`](https://metacpan.org/pod/Throwable::Error)**.

Using `Throwable` couldn’t be simpler:

```perl
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 subclass to get several useful methods:

```perl
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`](https://metacpan.org/pod/Throwable::Error#stack_trace) attribute comes courtesy of the [`StackTrace::Auto`](https://metacpan.org/pod/StackTrace::Auto) role composed into `Throwable::Error`. `Moo` and `Moose` users should simply compose it into their classes to get it.)

## Testing exceptions with [`Test::Exception`](https://metacpan.org/pod/Test::Exception)

Inevitably bugs will creep in to your code, and automated tests are one of the main weapons in a developer’s arsenal against them. **Use [`Test::Exception`](https://metacpan.org/pod/Test::Exception) when writing tests** against code that emits exceptions to see whether it behaves as expected:

```perl
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::Exception`’s functions don’t mess with `$EVAL_ERROR`, so you’re free to check its value right after you call it.

## Documenting errors and exceptions

If I can leave you with one message, it’s this: **Please document every error and exception your code produces**, preferably in a place and language that the end-user can understand. The DIAGNOSTICS section of your documentation _(you are writing documentation, right, not just code comments?)_ is a great candidate. You can model this section after the [`perldiag`](https://perldoc.pl/perldiag) manual page, which goes into great detail about many of the error messages generated by Perl itself.

_(A previous version of this article did not note that one should make sure a successful `eval` returns true, and incorrectly stated that `Class::Exception` and `Throwable` were deprecated due to [a bug in the MetaCPAN web site](https://github.com/metacpan/metacpan-web/issues/2438). Thanks to Dan Book for the corrections.)_