Stopping silent errors with exceptions

[ Perl tips index ]
[ Subscribe to Perl tips ]

This advice is deprecated. Use autodie instead.

Do you ever get sick of writing or die "Error: $!" after all of your calls to open? Do you remember to check whether your call to close worked? Do you remember to check whether you were able to print successfully to file handles? Do you check the return value of every subroutine you import from an existing module? How do you handle error situations for the many things that can go wrong when you use functions and subroutines which return a false value upon failure?

One way is to write code very much like the following:

        open(my $fh, "<", $filename) or die "Failed to open $filename: $!";

        print $fh "some text ..." or die "Failed to print to filehandle: $!";

        do_something_else() or die "Failed to do something!";

        close $fh or die "Failed to close filehandle: $!";

However, this is ugly and time consuming. Furthermore, most of the time print close, and other similar functions don't fail. This leads to the argument that writing your code as above, clutters your code to handle errors that ``never'' occur. Of course when they do occur and you're not checking the return values, you may find debugging the problem next to impossible.

In this tip we'll cover what exceptions are in Perl, why your subroutines should throw exceptions and, finally how to make Perl's existing subroutines and functions automatically throw exceptions on failure.

What is an Exception?

Put simply, an exception is what happens when something exceptional occurs. These are almost exclusively error cases. An exception usually interupts your program flow and, if not caught, causes your program to stop with an error.

When we write or die in Perl we are using Perl's exception handling system. Unless the die exception is caught, Perl will terminate the program and print out an error to STDERR.

We can also throw exceptions using the croak and confess subroutines from Perl's Carp module. These allow us to report errors from the perspective of the caller, which is useful if a subroutine has been called incorrectly or with bad arguments.

Catching Exceptions

We can catch the exception by using an eval block. Note that eval blocks are very different to a eval strings which we don't recommend. An eval block is compiled at the same time as our other code and runs in turn when it is reached. If an exception is thrown within an eval block then the eval stops and the $@ variable it set to the value of the exception.

        eval {
                open(my $fh, "<", $filename) 
                        or die "Failed to open $filename: $!";

                print $fh "some text ..." 
                        or die "Failed to print to filehandle: $!";

                do_something_else() 
                        or die "Failed to do something!";

                close $fh 
                        or die "Failed to close filehandle: $!";
        };

        # If an exception was thrown, catch it and do something
        if($@) {
                # log the error and do any other clean up...

                # rethrow the exception so that the program will die
                die "Failed to add data to file: $@";
        }

This still looks as ugly as it did before, but now we'll be able to do any cleanup we wanted to before the program terminates. This cleanup might include rolling back changes to the database, cleanly closing connections to other machines and sending an email to explain what went wrong.

Of course, a lot of the time we don't want to catch exceptions. When this is the case we can leave out the eval block as we did in the first example.

Using Exceptions

It's generally considered a good practice to write your subroutines to throw an exception upon failure rather than silently returning a false value. An exception is much more likely to grab the programmer's attention when something goes wrong. Even if it doesn't, an exception means that their program isn't going to carry on assuming that everything is working perfectly even though they haven't managed to make the connection, connect to the database or open the file.

To use exceptions just change your subroutines from looking like this:

        # Return the approximate pressure in atmospheres at a
        # given depth in metres underwater.

        sub pressure_at_depth {
                my $depth = shift;

                # We need to have an positive depth to return
                # a sensible result.
                return if not defined($depth)
                return if $depth < 0;

                return 1+($depth/10);
        }

To look like this:

        use Carp;

        sub pressure_at_depth {
                my $depth = shift;

                # We need to have an positive depth to return
                # a sensible result.
                croak "Depth not defined" if not defined($depth)
                croak "Positive depth required" if $depth < 0;

                return 1+($depth/10);
        }

Now if someone who is using your subroutines writes:

        my $pressure = pressure_at_depth($max_depth);

and $max_depth is undefined, then an error will seen immediately, rather than appearing further down in the program when $pressure is next used.

Using Exceptions Without Rewriting Code

It's not always possible or practical to rewrite existing code to use exceptions rather than to return undef. Doing so may break existing code which does test for errors but not by using exception handling. Further, these options don't help with Perl's built-in functions.

Fortunately it's possible to make Perl's built-in functions and existing subroutines thrown an exception on failure without having to rewrite anything. Even better, you can use this functionality in your new code without affecting the error handling of legacy programs. We can do this through the Perl module Fatal.

Fatal replaces functions with equivalents which succeed (return a true value) or die. It enables us to rewrite the code from our first example as:

        use Fatal qw(open close);

        open(my $fh, "<", $filename);

        print $fh "some text ..." or die "Failed to print to filehandle: $!";

        do_something_else() or die "Failed to do something!";

        close $fh;

Which is a huge step forward in readability while still ensuring exceptions are raised when open, or close fail. Unfortunately, Fatal cannot be used to catch errors from print. Fatal comes as a standard module with all recent releases of Perl. In addition, Fatal also allows us to do this for user-defined functions (subroutines).

        use Fatal qw(open close);
        
        # When using Fatal on a subroutine that's not exported
        # by a module, we need to call Fatal->import at run-time.

        Fatal->import("do_something_else");

        open(my $fh, "<", $filename);

        print $fh "some text ..." or die "Failed to print to filehandle: $!";

        do_something_else();

        close $fh;

[ Perl tips index ]
[ Subscribe to Perl tips ]


This Perl tip and associated text is copyright Perl Training Australia. You may freely distribute this text so long as it is distributed in full with this Copyright noticed attached.

If you have any questions please don't hesitate to contact us:

Email: contact@perltraining.com.au
Phone: 03 9354 6001 (Australia)
International: +61 3 9354 6001

Valid XHTML 1.0 Valid CSS