Exception::Class
El módulo Exception::Class permite implementar un control de errores basado en excepciones dentro de un programa ó biblioteca.
Los principios básicos son:
- Se declara una jerarquía de clases que permiten discernir qué clase de
error fatal ocurrió, ó a qué grupo pertenece (mediante el operador isa).
Esto presenta dos ventajas:
- No es necesario importar funciones ni variables en cada uno de los módulos, puesto que las clases creadas lo son globalmente para la aplicación.
- Al tener orientación a objetos se pueden derivar en clases nuevas, añadiendo ó quitando todo tipo de información, así como cambiando drásticamente la gestión de excepciones; tal vez unas graban algo en algún registro mientras que otras efectúan limpiezas en el sistema, las posibilidades son muchas.
- Se envuelve mediante eval todas las llamadas a código que es posible que provoque una excepción ó que muera ante una condición dada.
- Se comprueba inmediatamente después si ha habido una excepción, de qué tipo es y si podemos tratarla nosotros ó debemos lanzarla hacia atrás.
use Exception::Class ( 'MyException', 'MyException::System' => { isa => 'MyException', description => 'Operating system related failure', }, 'MyException::User' => { isa => 'MyException', description => 'User interaction realted failure', }, 'MyException::User::Keyboard' => { isa => 'MyException::User', description => 'Bad input from user', }, 'MyException::User::Gestures' => { isa => 'MyException::User', description => 'User did an offensive gesture ', }, );TRY: my $result = eval { my_code() };
my $ex; if ($ex = Exception::Class->caught('MyException')) { if (is_recoverable()) { next TRY; } else { $ex->rethrow(); } }
Y por si el método de capturar excepciones parece complicado, Exception::Class
proporciona algo de azucar sintáctico para ello:
TRY:eval { dangerous_code(); };
# Si el problema es por parte del usuario ... if (my $ex = MyException::User>caught()) { # si es un gesto ofensivo if ($ex->caught('MyException::User::Gestures'))) { # damos por terminado el proceso croak "unacceptable user input"; } else { # una nueva oportunidad ... next TRY; } } else { # es un error de sistema (ó cualquier otra cosa) que no podemos manejar $ex->rethrow(); }
Exception::Class dispone de un método heredable llamado caught() que acepta un nombre
de clase y devuelve un objeto if la última excepción es de dicha clase ó una
subclase de ella. Si no se le pasa ningún parámetro retorna el valor de $@.
Personalizando mensajes
Una de las ventajas de utilizar este tipo de gestión de errores está en la posibilidad de personalizar muchos de sus aspectos. En concreto el mensaje creado tras una excepción no capturada puede modificarse totalmente si definimos algunos métodos en el paquete que declara las clases:
package MyExceptions;use Exception::Class ( 'MyExceptions' => { description => 'Parent class', }, 'MyExceptions::FileSystem' => { isa => 'MyExceptions', description => 'Fatal error in file system access', fields => [ qw( errno file ) ], }, 'MyExceptions::Network' => { isa => 'MyExceptions', description => 'Fatal error in network access', fields => [ qw( host port errno ) ], }, );
sub fullmessage { my $self = shift; my @ret = (); foreach my $m ( $self->firstline(), $self->message(), $self->__fields() ) { push(@ret, $m) if $m; }
<span class="synStatement">return</span> <span class="synStatement">join</span>(<span class="synConstant">""</span>, <span class="synIdentifier">@ret</span>);}
sub _firstline { my $self = shift; my $programname = $0 || $self->file(); my $localtime = localtime($self->time()); my $package = $self->package(); my $file = $self->file(); my $line = $self->line(); my $pid = $self->pid(); my $uid = sprintf("uid=%u,gid=%u,euid=%u,egid=%u", $self->uid(), $self->gid(), $self->euid(), $self->egid()); $package = sprintf("package %s,", $package) if $package;
<span class="synStatement">return</span> <span class="synConstant"><<EOF;</span>${programname}(${pid}): error fatal in ${package} file ${file}, line ${line} at ${localtime} with ${uid} EOF }
sub __message { my $self = shift;
<span class="synStatement">my</span> <span class="synIdentifier">$ret</span> = <span class="synIdentifier">$self</span>->message() || <span class="synStatement">ref</span>(<span class="synIdentifier">$self</span>)->description() || <span class="synConstant">'unknown error'</span>; <span class="synStatement">return</span> <span class="synStatement">sprintf</span>(<span class="synConstant">"</span><span class="synSpecial">\n</span><span class="synConstant">%s</span><span class="synSpecial">\n\n</span><span class="synConstant">"</span>, <span class="synIdentifier">$ret</span>);}
sub __fields { my $self = shift; my @ret = ( );
<span class="synStatement">foreach</span> <span class="synStatement">my</span> <span class="synIdentifier">$f</span> (<span class="synStatement">ref</span>(<span class="synIdentifier">$self</span>)->Fields()) { <span class="synStatement">push</span>(<span class="synIdentifier">@ret</span>, <span class="synStatement">sprintf</span>(<span class="synConstant">" %s = %s</span><span class="synSpecial">\n</span><span class="synConstant">"</span>, <span class="synIdentifier">$f</span>, <span class="synIdentifier">$self</span>->{<span class="synIdentifier">$f</span>})); } <span class="synStatement">return</span> <span class="synIdentifier">@ret</span> ? (<span class="synConstant">" additional info:</span><span class="synSpecial">\n</span><span class="synConstant">"</span>, <span class="synIdentifier">@ret</span>) : ();}
En realidad, el método a sobrecargar es
full_message, el resto son añadidos para hacer la salida más informativa.
Lo anterior, quizás un tanto excesivo para incluir repetidamente en cada nuevo proyecto, daría como resultado un mensaje similar a éste:
test.pl(3456): error fatal in package main, file test.pl,
line 3 at lun abr 3 20:40:24 CEST 2006
with uid=1000,gid=1000,euid=1000,egid=1000
could not open password file
additional info:
errno = 1 operation not allowed
He dividido las funciones que extraen información porque me da más juego para obtenerla de varias fuentes, pero creo que es un ejemplo de lo que podría llegar a hacerse.
Nota: conviene advertir que para sobrecargar los métodos la primera clase
que se declara en la línea use Exception::Class no debe tener un atributo
isa específico. Exception::Class se encargará de convertirla en la clase
madre de todas las demás, y la herencia de Perl del resto para que la
sobrecarga funcione.




