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 librería 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