Passer plusieurs classes d'erreur à la clause de sauvetage de ruby ​​de manière DRY

101

J'ai du code qui doit sauver plusieurs types d'exceptions dans ruby:

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

Ce que je voudrais faire, c'est en quelque sorte stocker la liste des types d'exceptions que je veux sauver quelque part et passer ces types à la clause de sauvetage:

EXCEPTIONS = [FooException, BarException]

puis:

rescue EXCEPTIONS

Est-ce même possible, et est-ce possible sans quelques appels vraiment hack-y eval? Je n'ai pas bon espoir étant donné que je vois TypeError: class or module required for rescue clausequand je tente ce qui précède.

apb
la source
2
Qu'en est-il du sauvetage * EXCEPTIONS?
Roman

Réponses:

199

Vous pouvez utiliser un tableau avec l'opérateur splat *.

EXCEPTIONS = [FooException, BarException]

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

Si vous allez utiliser une constante pour le tableau comme ci-dessus (avec EXCEPTIONS), notez que vous ne pouvez pas la définir dans une définition, et aussi si vous la définissez dans une autre classe, vous devez y faire référence avec son espace de noms. En fait, ce n'est pas forcément une constante.


Opérateur Splat

L'opérateur splat *"décompresse" un tableau dans sa position de sorte que

rescue *EXCEPTIONS

signifie la même chose que

rescue FooException, BarException

Vous pouvez également l'utiliser dans un tableau littéral comme

[BazException, *EXCEPTIONS, BangExcepion]

qui est le même que

[BazException, FooException, BarException, BangExcepion]

ou en position d'argument

method(BazException, *EXCEPTIONS, BangExcepion)

ce qui signifie

method(BazException, FooException, BarException, BangExcepion)

[] s'étend à la vacuité:

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

Une différence entre ruby ​​1.8 et ruby ​​1.9 est avec nil.

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

Soyez prudent avec les objets sur lesquels to_aest défini, comme to_acela sera appliqué dans de tels cas:

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

Avec d'autres types d'objets, il revient tout seul.

[1, *2, 3] # => [1, 2, 3]
Sawa
la source
2
Cela semble fonctionner même dans ruby ​​1.8.7. Quel est le terme pour utiliser le caractère «*» devant EXCEPTIONSdans ce cas? Voudrais en savoir un peu plus.
apb
2
@Andy Ça s'appelle splat. Cela a généralement pour effet de décomposer un tableau en objets séparés par des virgules. Lorsqu'il est utilisé dans la position de réception d'argument d'une définition de méthode, il fait l'inverse: mettre les arguments ensemble dans un tableau. C'est très utile à diverses occasions. Bon à savoir qu'il fonctionne avec la version 1.8.7. J'ai modifié ma réponse en conséquence.
sawa
21
Notez que si vous souhaitez accéder à l'instance d'exception, utilisez cette syntaxe: rescue InvalidRequestError, CardError => e(voir mikeferrier.com/2012/05/19/… )
Peter Ehrlich
1
Cette syntaxe fonctionne très bien:, rescue *EXCEPTIONS => eEXCEPTIONSest un tableau de noms de classe d'exception.
aks le
3

Bien que la réponse donnée par @sawa soit techniquement correcte, je pense qu'elle abuse du mécanisme de gestion des exceptions de Ruby.

Comme le suggère le commentaire de Peter Ehrlich (en pointant vers un ancien article de blog de Mike Ferrier ), Ruby est déjà équipé d'un mécanisme de gestionnaire d'exceptions 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'

En utilisant cette technique, nous pouvons accéder à l'objet d'exception, qui contient généralement des informations précieuses.

Ron Klein
la source
1

Je viens de rencontrer ce problème et j'ai trouvé une solution alternative. Dans le cas où votre FooExceptionetBarException allez tous être des classes d'exceptions personnalisées et en particulier si elles sont toutes liées thématiquement, vous pouvez structurer votre hiérarchie d'héritage de telle sorte qu'elles hériteront toutes de la même classe parente, puis ne sauveront que la classe parente.

Par exemple , j'avais trois exceptions: FileNamesMissingError, InputFileMissingErroret OutputDirectoryErrorque je voulais sauver avec une déclaration. J'ai créé une autre classe d'exception appelée FileLoadError, puis j'ai configuré les trois exceptions ci-dessus pour en hériter. J'ai alors sauvé seulement FileLoadError.

Comme ça:

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
la source