Pasar varias clases de error a la cláusula de rescate de ruby ​​de forma SECA

100

Tengo un código que necesita rescatar varios tipos de excepciones en ruby:

begin
  a = rand
  if a > 0.5
    raise FooException
  else
    raise BarException
  end
rescue FooException, BarException
  puts "rescued!"
end

Lo que me gustaría hacer es almacenar de alguna manera la lista de tipos de excepción que quiero rescatar en algún lugar y pasar esos tipos a la cláusula de rescate:

EXCEPTIONS = [FooException, BarException]

y entonces:

rescue EXCEPTIONS

¿Es esto siquiera posible, y es posible sin algunas llamadas realmente hack-y a eval? No tengo esperanzas dado que estoy viendo TypeError: class or module required for rescue clausecuando intento lo anterior.

orden de busca y captura
fuente
2
¿Qué pasa con el rescate * EXCEPCIONES?
Roman

Respuestas:

197

Puede utilizar una matriz con el operador splat *.

EXCEPTIONS = [FooException, BarException]

begin
  a = rand
  if a > 0.5
    raise FooException
  else
    raise BarException
  end
rescue *EXCEPTIONS
  puts "rescued!"
end

Si va a usar una constante para la matriz como arriba (con EXCEPTIONS), tenga en cuenta que no puede definirla dentro de una definición, y también si la define en alguna otra clase, debe referirse a ella con su espacio de nombres. En realidad, no tiene por qué ser una constante.


Operador de Splat

El operador de splat *"desempaqueta" una matriz en su posición para que

rescue *EXCEPTIONS

significa lo mismo que

rescue FooException, BarException

También puede usarlo dentro de un literal de matriz como

[BazException, *EXCEPTIONS, BangExcepion]

que es lo mismo que

[BazException, FooException, BarException, BangExcepion]

o en una posición de argumento

method(BazException, *EXCEPTIONS, BangExcepion)

lo que significa

method(BazException, FooException, BarException, BangExcepion)

[] se expande al vacío:

[a, *[], b] # => [a, b]

Una diferencia entre ruby ​​1.8 y ruby ​​1.9 es con nil.

[a, *nil, b] # => [a, b]       (ruby 1.9)
[a, *nil, b] # => [a, nil, b]  (ruby 1.8)

Tenga cuidado con los objetos sobre los que to_aestá definido, ya to_aque se aplicará en tales casos:

[a, *{k: :v}, b] # => [a, [:k, :v], b]

Con otros tipos de objetos, vuelve a sí mismo.

[1, *2, 3] # => [1, 2, 3]
sawa
fuente
2
Esto parece funcionar incluso en ruby ​​1.8.7. ¿Cuál es el término para usar el carácter '*' delante de EXCEPTIONSen este caso? Me gustaría aprender un poco más.
apb
2
@Andy Se llama splat. Por lo general, tiene el efecto de descomponer una matriz en objetos separados por comas. Cuando se usa en la posición de recepción de argumentos de la definición de un método, lo hace al revés: junte los argumentos en una matriz. Es bastante útil en varias ocasiones. Es bueno saber que funciona con 1.8.7. Edité mi respuesta en consecuencia.
sawa
20
Tenga en cuenta que si desea acceder a la instancia de excepción, use esta sintaxis: rescue InvalidRequestError, CardError => e(consulte mikeferrier.com/2012/05/19/… )
Peter Ehrlich
Esta sintaxis funciona bien:, rescue *EXCEPTIONS => edonde EXCEPTIONSes una matriz de nombres de clases de excepción.
AKS
3

Si bien la respuesta dada por @sawa es técnicamente correcta, creo que hace un mal uso del mecanismo de manejo de excepciones de Ruby.

Como el comentario de Peter Ehrlich sugiere (señalando una publicación de blog antigua de Mike Ferrier ), Ruby ya está equipado con un mecanismo de manejo de excepciones DRY:

puts 'starting up'
begin
  case rand(3)
  when 0
    ([] + '')
  when 1
    (foo)
  when 2
    (3 / 0)
  end
rescue TypeError, NameError => e
  puts "oops: #{e.message}"
rescue Exception => e
  puts "ouch, #{e}"
end
puts 'done'

Al usar esta técnica, podemos acceder al objeto de excepción, que generalmente contiene información valiosa.

Ron Klein
fuente
1

Me encontré con este problema y encontré una solución alternativa. En el caso de que su FooExceptiony BarExceptiontodas sean clases de excepción personalizadas y, en particular, si están todas relacionadas temáticamente, puede estructurar su jerarquía de herencia de modo que todas hereden de la misma clase principal y luego rescaten solo la clase principal.

Por ejemplo, yo tenía tres excepciones: FileNamesMissingError, InputFileMissingErrory OutputDirectoryErrorque quería rescate con una sola declaración. Hice otra clase de excepción llamada FileLoadErrory luego configuré las tres excepciones anteriores para heredar de ella. Entonces solo rescaté FileLoadError.

Me gusta esto:

class FileLoadError < StandardError
end

class FileNamesMissingError < FileLoadError
end

class InputFileMissingError < FileLoadError
end

class OutputDirectoryError < FileLoadError
end

[FileNamesMissingError,
 InputFileMissingError,
 OutputDirectoryError].each do |error| 
   begin  
     raise error
   rescue FileLoadError => e
     puts "Rescuing #{e.class}."
   end 
end
Mikhail Golubitsky
fuente