[Bioperl-l] Exceptions and Interfaces

Steve A. Chervitz Steve_Chervitz@affymetrix.com
Sun, 22 Apr 2001 03:36:26 -0700


I put together a little demo of a neat way to throw
and handle exceptions within Perl that I'd like to propose
for adoption as a BioPerl standard. This also includes a module
(Bio::Root::Interface) that I'm proposing for use as a base class
for all BioPerl interface modules -- something that we've talked
on and off about having.

My demo is obtainable via ftp from bio.perl.org at:

ftp://bio.perl.org/pub/sac/bioperl-exceptions.tar.gz

For convenience, I'm including the README file for this demo below.

Let me know what you think.

Steve

Steve_Chervitz@affymetrix.com


--
README for BioPerl Exceptions

This directory contains some sample scripts and modules that
illustrate the use of Graham Barr's Error.pm module for creating,
throwing, and catching exceptions within Perl code. For demos, run the
scripts test-error.pl, test-error2.pl, test-noerror.pl.

Error.pm is available through CPAN and I encourage BioPerl users and
developers to install it and start using it in your scripts and
BioPerl modules. For your convenience, I've included a tarball
containing the latest Error.pm distribution from CPAN
(Error-0.13.tar.gz).

This module is quite convenient and easy to use and adds a level of
control for managing errors within your Perl code using familiar
object-oriented, try-catch-finally semantics. You can define subclasses
of Error.pm representing particular types of exceptions, and you can
define catch blocks to handle these types of exceptions.

This has distinct advantages over simply catching any and all errors
with an eval{} block, as is currently done in BioPerl. Strongly typed
exception objects make it easy to write appropriate handlers. It also
makes you code easier to understand because it's clear what type of
things can go wrong.

So why use Error.pm instead of other utility? Well, Perl 6 will most
likely include some form of structured exception handling akin to that
provided by Error.pm (see these RFC's: http://dev.perl.org/rfc/63.pod
and http://dev.perl.org/rfc/88.pod). So it will probably be easy to
convert Error.pm-based exception handling to whatever is adopted for
Perl 6.

(Side note for CORBA folks: Error.pm is used in some other CPAN
modules, notably CORBA::MICO. Thus, using Error.pm within BioPerl
allows consistent exception handling methodology when working with
CORBA::MICO and BioPerl together.)

Bio::Exception.pm
-----------------

The Bio::Exception.pm module contains a number of Error.pm subclasses
representing common types of errors. If you want to throw an exception
within your BioPerl module that doesn't correspond to any of the ones
defined in Bio::Exception, feel free to define a new one, but be sure
it inherits from Bio::Exception or one of its subclasses. This will
allow anyone to write a handler for any type of BioPerl exception.

Defining a new type of exception can be done quite simply. All you
need to do is to specify the @ISA array for your new type, as in:

     @Bio::Exception::MyBad::ISA = qw( Bio::Exception );

If you want to override any of the available methods or add new ones,
you'll have to provide a package statement and the appropriate
method definitions.

Programming tip: Be careful not to use exceptions as your primary
means of flow control within your code. Throwing and handling
exceptions come with some execution overhead. Also, such excessive use
of exceptions can make your logic hard to follow.

Bio::Root::Interface.pm
-----------------------

Also in this directory is the module Bio::Root::Interface.pm, that is
proposed for use as a base class for all BioPerl interfaces. It's
defines a method called "throw_not_implemented()" that simplifies the
process of throwing exceptions to indicate that a method has not been
implemented.

Bio::Root::Interface can make use of Error.pm if available, but
Error.pm is not required. The module TestInterface.pm demonstrates how
to use of Interface.pm

External Dependencies
---------------------

Using Error.pm within BioPerl adds an external dependency for a module
that is not part of the standard Perl distribution. It's easily
available via CPAN, but still, it would mean that BioPerl wouldn't
work "out of the box" if modules are written that depend on it. (A
reasonable short-term solution is to distribute Error.pm with
BioPerl. The tarball is only 8K.)

BioPerl modules that intend to use Error.pm should check for the
presence of it and call Error::throw() if it's present or
Bio::Root::RootI::throw() if it isn't. For example:

     my $message = "You supplied a bad parameter for foo: $foo";

     if( $Error_Is_Present ) {
	throw Bio::Exception::BadParameter ( -text => $message,
					    -value => $foo,
	                                    -object => $self );
     }
     else {
	$self->throw( $message );
     }

This isn't that much of a burden on developers. (Perhaps
Bio::Root::RootI::throw() could be modified to do this check, but it
would also have to know which class of Bio::Exception to throw.)

Here are some tips for determining if Error.pm is present:

1. If your module inherits from Bio::Root::Interface.pm, you can check
    $Bio::Root::Interface::Error_Is_Present for whether or not to call
    Error::throw() or $self->throw().

2. If your module doesn't inherit from Bio::Root::Interface.pm, put
    the following BEGIN block within your module to define your own
    $Error_Is_Present variable:

         BEGIN {
           use vars qw( $Error_Is_Present );
           $Error_Is_Present = 0;
           if( eval "require Error" ) {
	    import Error qw(:try);
	    require Bio::Exception;
             $Error_Is_Present = 1;
           }
         }


Note that I included Bio::Root::RootI in this directory. This is just a
copy of the standard BioPerl Bio:Root::RootI module and is included to
allow the examples to run stand-alone.

--
Steve Chervitz <steve_chervitz@affymetrix.com>
21 April 2001