Creative Commons License
Excepto donde se indique otra cosa, todo el contenido de este lugar está bajo una licencia de Creative Commons.
Taquiones > perl > cpan > Exception::Class

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.
    1 use Exception::Class (
    2     'MyException',
    3     'MyException::System' => {
    4         isa         =>  'MyException',
    5         description =>  'Operating system related failure',
    6         },
    7     'MyException::User' => {
    8         isa         =>  'MyException',
    9         description =>  'User interaction realted failure',
   10         },
   11     'MyException::User::Keyboard' => {
   12         isa         =>  'MyException::User',
   13         description =>  'Bad input from user',
   14         },
   15     'MyException::User::Gestures' => {
   16         isa         =>  'MyException::User',
   17         description =>  'User did an offensive gesture ',
   18         },
   19     );
   20 
   21 TRY:
   22 my $result = eval { my_code() };
   23 
   24 my $ex;
   25 if ($ex = Exception::Class->caught('MyException')) {
   26     if (is_recoverable()) {
   27         next TRY;
   28     }
   29     else {
   30         $ex->rethrow();
   31     }
   32 }

Y por si el método de capturar excepciones parece complicado, Exception::Class proporciona algo de azucar sintáctico para ello:

    1 TRY:
    2 
    3 eval {
    4     dangerous_code();
    5     };
    6 
    7 # Si el problema es por parte del usuario ...
    8 if (my $ex = MyException::User>caught()) {
    9     # si es un gesto ofensivo
   10     if ($ex->caught('MyException::User::Gestures'))) {
   11         # damos por terminado el proceso
   12         croak "unacceptable user input";
   13     }
   14     else {
   15         # una nueva oportunidad ...
   16         next TRY;
   17     }
   18 }
   19 else {
   20     # es un error de sistema (ó cualquier otra cosa) que no podemos manejar
   21     $ex->rethrow();
   22 }

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:

    1 package MyExceptions;
    2 
    3 use Exception::Class (
    4     'MyExceptions' => {
    5         description => 'Parent class',
    6         },
    7     'MyExceptions::FileSystem' => {
    8         isa         => 'MyExceptions',
    9         description => 'Fatal error in file system access',
   10         fields      => [ qw( errno file ) ],
   11         },
   12     'MyExceptions::Network' => {
   13         isa         =>  'MyExceptions',
   14         description =>  'Fatal error in network access',
   15         fields      =>  [ qw( host port errno ) ],
   16         },
   17 );
   18 
   19 sub full_message {
   20     my  $self   =   shift;
   21     my @ret = ();
   22     foreach my $m ( $self->__first_line(),
   23                     $self->__message(),
   24                     $self->__fields() ) {
   25         push(@ret, $m) if $m;
   26     }
   27 
   28     return join("", @ret);
   29 }
   30 
   31 sub __first_line {
   32     my $self = shift;
   33     my $program_name = $0 || $self->file();
   34     my $local_time = localtime($self->time());
   35     my $package = $self->package();
   36     my $file = $self->file();
   37     my $line = $self->line();
   38     my $pid = $self->pid();
   39     my $uid = sprintf("uid=%u,gid=%u,euid=%u,egid=%u",
   40                 $self->uid(), $self->gid(), $self->euid(),
   41                 $self->egid());
   42     $package = sprintf("package %s,", $package) if $package;
   43 
   44     return <<EOF;
   45 ${program_name}(${pid}): error fatal in ${package}
   46   file ${file}, line ${line} at ${local_time}
   47   with ${uid}
   48 EOF
   49 }
   50 
   51 sub __message {
   52     my $self = shift;
   53 
   54     my $ret = $self->message() || ref($self)->description()
   55             || 'unknown error';
   56 
   57     return sprintf("\n%s\n\n", $ret);
   58 }
   59 
   60 sub __fields {
   61     my $self = shift;
   62     my @ret = ( );
   63 
   64     foreach my $f (ref($self)->Fields()) {
   65         push(@ret, sprintf("    %s = %s\n", $f, $self->{$f}));
   66     }
   67 
   68     return @ret ? ("  additional info:\n", @ret) : ();
   69 }

En realidad el método a sobrecargar es full_message mientras que el resto son añadidos para hacer la salida más informativa.

Lo anterior es quizás un tanto excesivo para incluir repetidamente en cada nuevo proyecto; en cualquier caso 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.