Perl Programming Best Practices 2011 (part 2 - exceptions)

[ Perl tips index ]
[ Subscribe to Perl tips ]

Over the next few tips, we'll be covering current best practices for programming Perl. In this tip we talk about smart exception handling.

Exception handling

All of Perl's built-in functions are silent on failure. This means that we have to write a lot of code like this:

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

        while(<$fh>) {

        }

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

This is problematic, because if we forget to check for an error, then we may never know that there was a problem. Fortunately, from 5.10.1 autodie comes standard with Perl so now we can allow autodie to do our error handling for us. autodie changes all of Perl's built-in functions to throw an exception on failure.

        use autodie;

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

        while(<$fh>) {

        }

        close $fh;

Handling exceptions with Try::Tiny

The old way of throwing exceptions was to use block eval. This looks ugly, isn't consistent with other languages, and is easily confused with string eval.

        eval {
                die "Exception thrown";
        };

        if( $@ ) {
                warn "caught error: $@";
        }

Other languages use try and catch blocks for the same thing. try to run this code, and catch any errors it might generate. With Try::Tiny we can have the same semantics:

        use Try::Tiny;

        try {
                die "Exception thrown";
        }
        catch {
                warn "caught error: $_";
        };

There's also a finally block which will be run, whether or not any exceptions were thrown and caught.

Throwing exceptions from the perspective of the caller

In your code, and especially in your modules, you should also throw exceptions on failure. This means that errors will not be silently ignored while the program does the wrong thing. If, however, you are throwing an exception from a module, you probably don't want the error message to point to the line in your module that threw the error, but rather the line from which your subroutine was erroneously called.

For example this package allows us to generate greetings in different languages:

        package Greeting;

        my %greeting_in = ( 'en-au' => "G'day" );

        sub greet {
                my ($language) = @_;

                my $greeting = $greeting_in{$language} 
                        or die "Don't know how to greet in $language";

                return $greeting;
        }

        1;

If we call it with a language we haven't defined:

        use Greeting;

        print Greeting::greet('jp'), "\n";

We're told which line in the module had errors, but not from where it was called:

        Don't know how to greet in jp at Greeting.pm line 8.

Using Carp allows us to throw the exception from the perspective of the caller:

        package Greeting;
        use Carp;

        my %greeting_in = ( 'en-au' => "G'day" );

        sub greet {
                my ($language) = @_;

                my $greeting = $greeting_in{$language} 
                        or croak "Don't know how to greet in $language";

                return $greeting;
        }

        1;

Now we get:

        Don't know how to greet in jp at test.pl line 4

Next Up

In the next tip we'll talk about handling multiple versions of Perl, even if you don't have system access.

[ 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