Programación en Ruby/Manejo de excepciones

Una excepción es una clase de objeto especial, una instancia de la clase Exception o de una clase descendiente de esa clase que representa una condición especial; indica que algo ha salido mal. Cuando esto ocurre, se genera una excepción. Por defecto, los programas Ruby terminan cuando una excepción ocurre pero es posible escribir código que maneje estas excepciones.

Generar una excepción significa detener la ejecución normal del programa y transferir el control al código que maneja la excepción en donde puedes ocuparte del problema que ha sido encontrado o bien, terminar la ejecución del programa. Una de estas dos opciones (solucionar el problema de alguna manera o detener la ejecución de programa) ocurre dependiendo de si has proporcionado una cláusula rescue. Si no has proporcionado dicha cláusula, el programa termina, por el contrario, si la cláusula existe, el control de ejecución fluye hacia esta.

Ruby tiene algunas clases predefinidas -descendientes de la clase Exception- que te ayudan a manejar errores que ocurren en tu programa.

El siguiente método genera una excepción cada vez que es llamado. El segundo mensaje nunca va a ser mostrado.

1 def genera_excepcion
2         puts 'Antes de la excepcion.'
3         raise 'Ha ocurrido un error'
4         puts 'Despues de la excepcion'
5 end
6 
7 genera_excepcion

El método raise está definido en el módulo Kernel. Por defecto, raise genera una excepción de la clase RuntimeError. Para generar una excepción de una clase en específico, puedes pasar el nombre de la clase como argumento al método raise.

1 def inverse(x)
2   raise ArgumentError, 'El argumento no es un numero' unless x.is_a? Numeric
3   1.0 / x
4 end
5 puts inverse(2)
6 puts inverse('hola')

Manejando una excepción Para manejar excepciones (handle exceptions), incluímos el código que pueda generar una excepción en un bloque begin-end y usamos una o más cláusulas rescue para indicarle a Ruby los tipos de excepción que queremos manejar. Es importante notar que el cuerpo de la definición de un método es un bloque begin-end explícito; begin es omitido y todo el cuerpo de la definición del método está sujeto al manejo de excepciones hasta que aparezca la palabra end.

1 def genera_y_rescata
2   begin        
3           puts 'Estoy antes de raise.'
4                 raise 'Ha ocurrido un error.'
5                 puts 'Estoy despues de raise.'
6         rescue
7           puts 'He sido rescatado.'
8         end
9         puts 'Estoy despues de begin.'
10 end
11 genera_y_rescata

Observa que el código interrumpido por la excepción nunca es ejecutado. Una vez que la excepción es rescatada, la ejecución continúa inmediatamente después del bloque begin que la generó.

Puedes apilar cláusulas rescue en un bloque begin-end. Las excepciones que no sean manejadas por una cláusula rescue fluirán hacia la siguiente:

1 begin
2   # ...
3 rescue UnTipoDeExcepcion
4   # ...
5 rescue OtroTipoDeExcepcion
6   # ..
7 else
8   # Otras excepciones
9 end

Para cada cláusula rescue en el bloque begin, Ruby compara la excepción generada con cada uno de los parámetros en turno. La ocurrencia tiene éxito si la excepción nombrada en la cláusula rescue es del mismo tipo que la excepción generada. El código en una cláusula else es ejecutado si el código en la expresiôn begin es ejecutado sin excepciones. Si una excepción ocurre, entonces la cláusula else no es ejecutada. El uso de una cláusula eles no es particularmente común en Ruby. Si quieres interrogar a una excepción rescatada, puedes asignar el objeto de clase Exception a una variable en la cláusula rescue, como se muestra en el programa p046excpvar.rb

1 begin
2   raise 'Una excepcion.'
3 rescue Exception => e
4   puts e.message
5         puts e.backtrace.inspect
6 end

La clase Exception define dos métodos que regresan detalles acerca de la excepción. El método message regresa una cadena que puede proporcionar detalles legibles acerca de lo que ocurrió mal. El otro método importante es backtrace. Este método regresa un array de cadenas que representa la pila de ejecución hasta el punto en que la excepción fue generada. Si necesitas garantizar que algún proceso es ejecutado al final de un bloque de código sin importar si se generó una excepción o no, puedes usar la cláusula ensure. ensure va al final de la última cláusula rescue y contiene un bloque de código que siempre va a ser ejecutado.

Excepciones con Manejo de Archivos

Ejemplo:

1 # Abrir un archivo y leer su contenido
2 # Nota que ya que está presente un bloque, el archivo
3 # es cerrado automaticamente cuando se termina la ejecucion
4 # del bloque
5 begin
6   File.open('p014estructuras.rb', 'r') do |f1|
7     while linea = f1.gets
8       puts linea
9     end
10   end
11 
12   # Crer un archivo y escribir en el
13   File.open('prueba.txt', 'w') do |f2|
14     f2.puts "Creado desde un programa Ruby!"
15   end
16 rescue Exception => msg
17   # mostar el mensaje de error generado por el sistema
18   puts msg
19 end