Passar várias classes de erro para a cláusula de resgate do Ruby de maneira DRY


100

Eu tenho um código que precisa resgatar vários tipos de exceções em Ruby:

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

O que eu gostaria de fazer é de alguma forma armazenar a lista de tipos de exceção que desejo resgatar em algum lugar e passar esses tipos para a cláusula de resgate:

EXCEPTIONS = [FooException, BarException]

e depois:

rescue EXCEPTIONS

Isso é mesmo possível, e é possível sem algumas chamadas realmente hack-y para eval? Não estou esperançoso, visto que estou vendo TypeError: class or module required for rescue clausequando tento o acima.


2
E sobre resgate * EXCEÇÕES?
Romano,

Respostas:


197

Você pode usar uma matriz com o operador splat *.

EXCEPTIONS = [FooException, BarException]

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

Se você for usar uma constante para a matriz como acima (com EXCEPTIONS), observe que você não pode defini-la dentro de uma definição, e também se você definir em alguma outra classe, você deve se referir a ela com seu namespace. Na verdade, não precisa ser uma constante.


Operador Splat

O operador splat *"desempacota" uma matriz em sua posição para que

rescue *EXCEPTIONS

significa o mesmo que

rescue FooException, BarException

Você também pode usá-lo em um literal de array como

[BazException, *EXCEPTIONS, BangExcepion]

que é o mesmo que

[BazException, FooException, BarException, BangExcepion]

ou em uma posição de argumento

method(BazException, *EXCEPTIONS, BangExcepion)

que significa

method(BazException, FooException, BarException, BangExcepion)

[] expande para vazio:

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

Uma diferença entre o rubi 1.8 e o rubi 1.9 é com nil.

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

Tenha cuidado com os objetos em que to_aestá definido, como to_aserá aplicado em tais casos:

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

Com outros tipos de objetos, ele retorna a si mesmo.

[1, *2, 3] # => [1, 2, 3]

2
Isso parece funcionar até mesmo no Ruby 1.8.7. Qual é o termo para usar o caractere '*' na frente de EXCEPTIONSneste caso? Gostaria de aprender um pouco mais.
apb

2
@Andy É chamado de splat. Geralmente, tem o efeito de decompor uma matriz em objetos separados por vírgulas. Quando usado na posição de recebimento de argumento de uma definição de método, ele faz o contrário: coloca os argumentos juntos em um array. É bastante útil em várias ocasiões. É bom saber que funciona com 1.8.7. Eu editei minha resposta de acordo.
sawa

20
Observe que se você deseja acessar a instância de exceção, use esta sintaxe: rescue InvalidRequestError, CardError => e(consulte mikeferrier.com/2012/05/19/… )
Peter Ehrlich

Esta sintaxe funciona perfeitamente:, rescue *EXCEPTIONS => eonde EXCEPTIONSé um array de nomes de classes de exceção.
aks

3

Embora a resposta dada por @sawa esteja tecnicamente correta, acho que ela usa mal o mecanismo de tratamento de exceções do Ruby.

Como o comentário de Peter Ehrlich sugere (apontando para um antigo post de blog de Mike Ferrier ), Ruby já está equipado com um mecanismo de tratamento de exceção 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'

Usando essa técnica, podemos acessar o objeto de exceção, que geralmente contém algumas informações valiosas.


1

Acabei de encontrar esse problema e encontrei uma solução alternativa. No caso do seuFooException eBarException serão todas classes de exceção personalizadas e, particularmente, se forem todas relacionadas de forma temática, você pode estruturar sua hierarquia de herança de forma que todas herdem da mesma classe pai e resgatem apenas a classe pai.

Por exemplo, eu tinha três exceções: FileNamesMissingError, InputFileMissingErrore OutputDirectoryErrorque eu queria resgate com um comunicado. Eu fiz outra classe de exceção chamada FileLoadErrore, em seguida, configurei as três exceções acima para herdar dela. Eu então resgatei apenasFileLoadError .

Como isso:

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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.