[Perl.sig] Perl Tip - Stopping Silent Errors With Exceptions

Litss Coordinator litss.coord at anu.edu.au
Wed Apr 13 12:00:23 EST 2005


==== News ====

     Stas Bekman, leading mod_perl developer, and author of *Practical
     mod_perl* will be presenting courses in Melbourne and Sydney during
June
     2005. Places must be booked by ``29th May 2005''. See the end of this
     tip for more information.


==== Stopping Silent Errors With Exceptions ====

     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 print close);

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

             print $fh "some text ...";

             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'', ``print'' or ``close'' fail.
     ``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 print 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 ...";

             do_something_else();

             close $fh;


==== Upcoming Courses in Canberra ====

     http://perltraining.com.au/bookings/Canberra.html

         Introduction to Perl:               17th - 18th May
         Intermediate Perl:                  19th - 20th May
         Database Programming with Perl:            23rd June
         Perl Security:                             24th June

     Note that the ``Early Bird Date'' for the introduction courses is
     Friday, this week: ``15 April 2005''!.


==== Upcoming Courses in Melbourne ====

     http://perltraining.com.au/bookings/Melbourne.html

         Database Programming with Perl:            26th May
         Perl Security:                             27th May
         Getting started with mod_perl:              6th June
         mod_perl 2.0, the next generation:          7th June
         Object Oriented Perl:                7th -  8th July

     Note that the ``Early Bird Date'' for the first four courses is
     ``29th April 2005''!.


==== Upcoming Courses in Sydney ====

     http://perltraining.com.au/bookings/Sydney.html

         Database Programming with Perl:             9th June
         Perl Security:                             10th June
         Getting started with mod_perl:             13th June
         mod_perl 2.0, the next generation:         14th June
         Object Oriented Perl:               21st - 22nd July

     Note that the ``Early Bird Date'' for the mod_perl courses is
     ``29th April 2005''!.


==== Corporate Courses ====

     http://perltraining.com.au/corporate.html

     Do you have a large group, or the need for training at a particular
     time? Perl Training Australia is happy to arrange a course in the time
     and place that best suits you. For more information read our page on
     Corporate Courses at http://perltraining.com.au/corporate.html or call
     us on +61-3-9354-6001.
_______________________________________________
This Perl tooltip 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 at perltraining.com.au
   Phone: 03 9354 6001

Perl-tips mailing list
To change your subscription details visit:
http://perltraining.com.au/cgi-bin/mailman/listinfo/perl-tips





More information about the Perl.sig mailing list