[ 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.
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.
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.
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.
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 |
Copyright Perl Training Australia. Contact us at contact@perltraining.com.au