|
Exception::Base - Lightweight exceptions |
Exception::Base - Lightweight exceptions
# Use module and create needed exceptions
use Exception::Base (
':all', # import try/catch functions
'Exception::Runtime', # create new module
'Exception::System', # load existing module
'Exception::IO', => {
isa => 'Exception::System' }, # create new based on existing
'Exception::FileNotFound' => {
message => 'File not found',
isa => 'Exception::IO' }, # create new based on new
);
# try / catch
try eval {
do_something() or throw Exception::FileNotFound
message=>'Something wrong',
tag=>'something';
};
# Catch the Exception::Base and derived, rethrow immediately others
if (catch my $e) {
# $e is an exception object for sure, no need to check if is blessed
if ($e->isa('Exception::IO')) { warn "IO problem"; }
elsif ($e->isa('Exception::Eval')) { warn "eval died"; }
elsif ($e->isa('Exception::Runtime')) { warn "some runtime was caught"; }
elsif ($e->with(tag=>'something')) { warn "something happened"; }
elsif ($e->with(qr/^Error/)) { warn "some error based on regex"; }
else { $e->throw; } # rethrow the exception
}
# the exception can be thrown later $e = new Exception::Base; # (...) $e->throw;
# try with array context
@v = try [eval { do_something_returning_array(); }];
# catch only IO errors, rethrow immediately others
try eval { File::Stat::Moose->stat("/etc/passwd") };
catch my $e, ['Exception::IO'];
# immediately rethrow all caught exceptions and eval errors
try eval { die "Bang!\n" };
catch my $e, [];
# don't use syntactic sugar
use Exception::Base; # does not import ':all' tag
try Exception::Base eval {
throw Exception::IO;
};
catch Exception::Base my $e; # catch Exception::Base and derived
# or
catch Exception::IO my $e; # catch IO errors and rethrow others
This class implements a fully OO exception mechanism similar to the Exception::Class manpage or the Class::Throwable manpage. It does not depend on other modules like the Exception::Class manpage and it is more powerful than the Class::Throwable manpage. Also it does not use closures as the Error manpage and does not polute namespace as the Exception::Class::TryCatch manpage. It is also much faster than the Exception::Class manpage.
The features of the Exception::Base manpage:
use Exception::Base qw< catch try >;
try eval { throw Exception::Base; };
if (catch my $e) { warn "$e"; }
use Exception::Base qw< Exception::Custom Exception::SomethingWrong >; throw Exception::Custom;
use Exception::Base
'try', 'catch',
'Exception::IO',
'Exception::FileNotFound' => { isa => 'Exception::IO' },
'Exception::My' => { version => 0.2 },
'Exception::WithDefault' => { message => 'Default message' };
try eval { throw Exception::FileNotFound; };
if (catch my $e) {
if ($e->isa('Exception::IO')) { warn "can be also FileNotFound"; }
if ($e->isa('Exception::My')) { print $e->VERSION; }
}
use Exception::Base ':all', 'Exception::FileNotFound';
try eval { throw Exception::FileNotFound; }; # ok
no Exception::Base;
try eval { throw Exception::FileNotFound; }; # syntax error
The fields are listed as name => {properties}, where properties is a list of field properties:
The read-write fields can be set with new constructor. Read-only fields are modified by the Exception::Base manpage class itself and arguments for new constructor will be stored in properties field.
The constant have to be defined in derivered class if it brings additional fields.
package Exception::My; our $VERSION = 0.01; use base 'Exception::Base';
# Define new class fields
use constant FIELDS => {
%{Exception::Base->FIELDS}, # base's fields have to be first
readonly => { is=>'ro', default=>'value' }, # new ro field
readwrite => { is=>'rw' }, # new rw field
};
package main;
use Exception::Base ':all';
try eval {
throw Exception::My readonly=>1, readwrite=>2;
};
if (catch my $e) {
print $e->{readwrite}; # = 2
print $e->{properties}->{readonly}; # = 1
print $e->{defaults}->{readwrite}; # = "value"
}
Class fields are implemented as values of blessed hash. The fields are also available as accessors methods.
eval { throw Exception message=>"Message", tag=>"TAG"; };
print $@->{message} if $@;
eval { throw Exception message=>"Message", tag=>"TAG"; };
print $@->{properties}->{tag} if $@;
Message
Message at %s line %d.
The same as the standard output of die() function.
Class: Message at %s line %d
%c_ = %s::%s() called at %s line %d
...
The output contains full trace of error stack. This is the default option.
If the verbosity is undef, then the default verbosity for exception objects is used.
If the verbosity set with constructor (new or throw) is lower than 3, the full stack trace won't be collected.
If the verbosity is lower than 2, the full system data (time, pid, tid, uid, euid, gid, egid) won't be collected.
package My::Package;
use Exception::Base;
sub my_function {
do_something() or throw Exception::Base ignore_package=>__PACKAGE__;
}
# Convert warning into exception. The signal handler ignores itself.
use Exception::Base 'Exception::Warning';
$SIG{__WARN__} = sub {
Exception::Warning->throw(message => $_[0], ignore_level => 1)
};
try eval {
eval { $a = $b = 0; $c = $a / $b };
throw Exception::Base;
};
catch my $e;
print $e->eval_error;
eval { throw Exception message=>"Message"; };
print scalar localtime $@->{time};
eval { throw Exception message=>"Message"; };
kill 10, $@->{pid};
caller() function. Further elements are optional
and are the arguments of called function. Collected if the verbosity on
throwing exception was greater than 1. Contains only the first element of
caller stack if the verbosity was lower than 3.
eval { throw Exception message=>"Message"; };
($package, $filename, $line, $subroutine, $hasargs, $wantarray,
$evaltext, $is_require, @args) = $@->{caller_stack}->[0];
sub a { throw Exception max_arg_len=>5 }
a("123456789");
sub a { throw Exception max_arg_nums=>1 }
a(1,2,3);
eval "throw Exception max_eval_len=>10"; print "$@";
my $e = new Exception;
print defined $e->{verbosity}
? $e->{verbosity}
: $e->{defaults}->{verbosity};
If the key of the argument is read-write field, this field will be filled. Otherwise, the properties field will be used.
$e = new Exception message=>"Houston, we have a problem",
tag => "BIG";
print $e->{message};
print $e->{properties}->{tag};
The constructor reads the list of class fields from FIELDS constant function and stores it in the internal cache for performance reason. The defaults values for the class are also stored in internal cache.
die() function.
open FILE, $file
or throw Exception message=>"Can not open file: $file";
$e = new Exception::Base; # (...) $e->throw(message=>"thrown exception with overriden message");
eval { throw Exception::Base message=>"Problem", fatal=>1 };
$@->throw if $@->properties->{fatal};
eval { open $f, "w", "/etc/passwd" or throw Exception::System };
# convert Exception::System into Exception::Base
throw Exception::Base $@;
eval { throw Exception; };
$@->{verbosity} = 1;
print "$@";
print $@->stringify(3) if $VERY_VERBOSE;
It also replaces any message stored in object with the message argument if it exists. This feature can be used by derived class overwriting stringify method.
$e->with("message");
$e->with(tag=>"property");
$e->with("message", tag=>"and the property");
$e->with(tag1=>"property", tag2=>"another property");
$e->with(uid=>0);
$e->with(message=>'$e->{properties}->{message} or $e->{message}');
The argument (for message or properties) can be simple string or code reference or regexp.
$e->with("message");
$e->with(sub {/message/});
$e->with(qr/message/);
try Exception::Base eval { throw Exception; };
eval { die "another error messing with \$@ variable"; };
catch Exception::Base my $e;
The try returns the value of the argument in scalar context. If the argument is array reference, the try returns the value of the argument in array context.
$v = try Exception::Base eval { 2 + 2; }; # $v == 4
@v = try Exception::Base [ eval { (1,2,3); }; ]; # @v = (1,2,3)
The try can be used as method or function.
try Exception::Base eval { throw Exception::Base "method"; };
Exception::Base::try eval { throw Exception::Base "function"; };
Exception::Base->import('try');
try eval { throw Exception::Base "exported function"; };
try eval { throw Exception; };
catch Exception::Base my $e;
print $e->stringify(1);
If the error stack is empty, the catch method returns undefined value. It can be used in loop to clean up all unhandled exceptions.
try eval { -f 'file1' or throw Exception::FileNotFound };
try eval { -f 'file2' or throw Exception::FileNotFound };
try eval { -f 'file3' or throw Exception::FileNotFound };
while (catch my $e) {
warn "$e" if not $e->isa('Exception::FileNotFound');
}
If the $@ variable does not contain the exception object but string, new
exception object is created with message from $@ variable with removed
" at file line 123." string and the last end of line (LF).
try eval { die "Died\n"; };
catch Exception::Base my $e;
print $e->stringify;
The method returns 1, if the exception object is caught, and returns 0 otherwise.
try eval { throw Exception; };
if (catch Exception::Base my $e) {
warn "Exception caught: " . ref $e;
}
If the method argument is missing, the method returns the exception object.
try eval { throw Exception; };
my $e = catch Exception::Base;
The catch can be used as method or function. If it is used as function, then the CLASS is Exception::Base by default.
try eval { throw Exception::Base "method"; };
Exception::Base->import('catch');
catch my $e; # the same as catch Exception::Base my $e;
print $e->stringify;
try eval { throw Exception::IO; }
catch Exception::Base my $e, ['Exception::IO'];
print "Only IO exception was caught: " . $e->stringify(1);
package Exception::Special;
use base 'Exception::Base';
use constant FIELDS => {
%{Exception::Base->FIELDS},
'special' => { is => 'ro' },
};
sub _collect_system_data {
my $self = shift;
$self->SUPER::_collect_system_data(@_);
$self->{special} = get_special_value();
return $self;
}
__PACKAGE__->_make_accessors;
1;
Method returns the reference to the self object.
package Exception::My; # (...) __PACKAGE__->_make_accessors;
There are more implementation of exception objects available on CPAN:
See also the Exception::System manpage class as an example for implementation of echanced exception class based on this the Exception::Base manpage class.
The the Exception::Base manpage module was benchmarked with other implementations for simple try/catch scenario. The results (Perl 5.8.8 i486-linux-gnu-thread-multi) are following:
The the Exception::Base manpage module is 80 times slower than pure eval/die. This module was written to be as fast as it is possible. It does not use i.e. accessor functions which are slower about 6 times than standard variables. It is slower than pure die/eval because it is uses OO mechanism which are slow in Perl. It can be a litte faster if some features are disables, i.e. the stack trace and higher verbosity.
You can find the benchmark script in this package distribution.
The module was tested with the Devel::Cover manpage and the Devel::Dprof manpage.
If you find the bug, please report it.
Piotr Roszatycki <dexter@debian.org>
Copyright (C) 2007 by Piotr Roszatycki <dexter@debian.org>.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
See http://www.perl.com/perl/misc/Artistic.html
|
Exception::Base - Lightweight exceptions |