Capítulo 22. Procesamiento de excepciones: rescue

Un programa en ejecución puede encontrarse con problemas inesperados. Podría no existir un fichero que desea leer, al salvar algunos datos se podría llenar un disco, un usuario podría introducir algún tipo de datos de entrada poco adecuados.


ruby> file = open("algun_fichero")
ERR: (eval):1:in `open': No such file or directory - "algun_fichero"

Un programa robusto manejará estas situaciones prudente y elegantemente. Satisfacer estas expectativas puede ser una tarea exasperante. Los programadores en C se supone que deben verificar toda llamada al sistema que pudiese fallar y decidir inmediatamente que hacer.


FILE *file = fopen("algun_fichero","r");
if (file == NULL) {
  fprintf(stderr, "No existe el fichero\n");
  exit(1);
}
bytes_read = fread(buf,1,bytes_desired,file);
if (bytes_read != bytes_desired) {
  /* aquí, más gestión de errores ... */
}
...

Con esta práctica tan aburrida los programadores tienden a ser descuidados y la incumplen siendo el resultado un programa que no gestiona adecuadamente las excepciones. Por otro lado, si se realiza adecuadamente, los programas se vuelven ilegibles debido a que hay mucha gestión de errores que embrolla el código significativo.

En Ruby, como en muchos lenguajes modernos, se pueden gestionar las excepciones para bloques de código de una forma compartimentalizada, lo que permite tratar los imprevistos de forma efectiva sin cargar excesivamente ni al programador ni a cualquier otra persona que intente leer el código posteriormente. El bloque de código marcado con begin se ejecutará hasta que haya una excepción, lo que provoca que el control se transfiera a un bloque con el código de gestión de errores, aquel marcado con rescue. Si no hay excepciones, el código de rescue no se usa. El siguiente método devuelve la primera línea de un fichero de texto o nil si hay una excepción.


def first_line( filename )
  begin
    file = open(filename)
    info = file.gets
    file.close
    info # Lo último que se evalúa es el valor devuelto
  rescue
    nil # No puedo leer el fichero, luego no devuelvo una cadena
  end
end

A veces nos gustaría evitar con creatividad un problema. A continuación, si el fichero no existe, se prueba a utilizar la entrada estándar:


begin
  file = open("algun_fichero")
rescue
  file = STDIN
end
begin
  # ... procesamos la entrada ...
rescue
  # ... aquí tratamos cualquier otra excepción
end

Dentro del código de rescue se puede utilizar retry para intentar de nuevo el código en begin. Esto nos permite reescribir el ejemplo anterior de una forma más compacta:


fname = "algun_fichero"
begin
  file = open(fname)
  # ... procesamos la entrada ...
rescue
  fname = "STDIN"
  retry
end

Sin embargo, este ejemplo tiene un punto débil. Si el fichero no existe este reintento entrará en un bucle infinito. Es necesario estar atento a estos escollos cuando se usa retry en el procesamiento de excepciones.

Toda biblioteca Ruby genera una excepción si ocurre un error y se pueden lanzar excepciones explícitamente dentro del código. Para lanzar una excepción utilizamos raise. Tiene un argumento, la cadena que describe la excepción. El argumento es opcional pero no se debería omitir. Se puede acceder a él posteriormente a través de la variable global especial $!.


ruby> begin
ruby|   raise "error"
ruby| rescue
ruby|   print "Ha ocurrido un error: ", $!, "\n"
ruby| end
Ha ocurrido un error: error
nil