Übergabe mehrerer Fehlerklassen an die Rettungsklausel von Ruby auf DRY-Weise

100

Ich habe einen Code, der mehrere Arten von Ausnahmen in Ruby retten muss:

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

Ich möchte irgendwie die Liste der Ausnahmetypen speichern, die ich irgendwo retten möchte, und diese Typen an die Rettungsklausel übergeben:

EXCEPTIONS = [FooException, BarException]

und dann:

rescue EXCEPTIONS

Ist das überhaupt möglich und ist es möglich ohne einige wirklich hackige Anrufe eval? Ich bin nicht hoffnungsvoll, da ich sehe, TypeError: class or module required for rescue clausewenn ich das oben genannte versuche.

apb
quelle
2
Was ist mit Rettung * AUSNAHMEN?
Roman

Antworten:

197

Sie können ein Array mit dem Splat-Operator verwenden *.

EXCEPTIONS = [FooException, BarException]

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

Wenn Sie eine Konstante für das Array wie oben (mit EXCEPTIONS) verwenden möchten, beachten Sie, dass Sie sie nicht innerhalb einer Definition definieren können. Wenn Sie sie auch in einer anderen Klasse definieren, müssen Sie mit ihrem Namespace darauf verweisen. Eigentlich muss es keine Konstante sein.


Splat-Operator

Der Splat-Operator *"entpackt" ein Array an seiner Position, so dass

rescue *EXCEPTIONS

bedeutet das gleiche wie

rescue FooException, BarException

Sie können es auch in einem Array-Literal als verwenden

[BazException, *EXCEPTIONS, BangExcepion]

das ist das gleiche wie

[BazException, FooException, BarException, BangExcepion]

oder in einer Argumentationsposition

method(BazException, *EXCEPTIONS, BangExcepion)

was bedeutet

method(BazException, FooException, BarException, BangExcepion)

[] erweitert sich auf Leerstand:

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

Ein Unterschied zwischen Rubin 1.8 und Rubin 1.9 besteht bei nil.

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

Seien Sie vorsichtig mit Objekten, auf die to_adefiniert ist, wie to_ain solchen Fällen angewendet wird:

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

Bei anderen Objekttypen gibt es sich selbst zurück.

[1, *2, 3] # => [1, 2, 3]
sawa
quelle
2
Dies scheint sogar in Ruby 1.8.7 zu funktionieren. Was ist EXCEPTIONSin diesem Fall der Begriff für die Verwendung des Zeichens '*' vor ? Möchte ein bisschen mehr lernen.
Apb
2
@ Andy Es heißt Splat. Dies hat normalerweise den Effekt, dass ein Array in durch Kommas getrennte Objekte zerlegt wird. Bei Verwendung in der Argumentempfangsposition einer Methodendefinition erfolgt die umgekehrte Vorgehensweise: Fügen Sie die Argumente zu einem Array zusammen. Es ist sehr nützlich bei verschiedenen Gelegenheiten. Gut zu wissen, dass es mit 1.8.7 funktioniert. Ich habe meine Antwort entsprechend bearbeitet.
sawa
20
Beachten Sie, dass Sie, wenn Sie auf die Ausnahmeinstanz zugreifen möchten, diese Syntax verwenden: rescue InvalidRequestError, CardError => e(siehe mikeferrier.com/2012/05/19/… )
Peter Ehrlich
Diese Syntax funktioniert einwandfrei : rescue *EXCEPTIONS => e, wobei EXCEPTIONSsich ein Array von Ausnahmeklassennamen befindet.
aks
3

Obwohl die Antwort von @sawa technisch richtig ist, denke ich, dass sie Rubys Ausnahmebehandlungsmechanismus missbraucht.

Wie der Kommentar von Peter Ehrlich nahe legt (indem er auf einen alten Blog-Beitrag von Mike Ferrier verweist ), ist Ruby bereits mit einem DRY-Ausnahmebehandlungsmechanismus ausgestattet:

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'

Mit dieser Technik können wir auf das Ausnahmeobjekt zugreifen, das normalerweise einige wertvolle Informationen enthält.

Ron Klein
quelle
1

Ich bin gerade auf dieses Problem gestoßen und habe eine alternative Lösung gefunden. In dem Fall, dass Ihre FooExceptionund BarExceptionalle benutzerdefinierte Ausnahmeklassen sind und insbesondere wenn sie alle thematisch miteinander verbunden sind, können Sie Ihre Vererbungshierarchie so strukturieren, dass sie alle von derselben übergeordneten Klasse erben und dann nur die übergeordnete Klasse retten.

Zum Beispiel hatte ich drei Ausnahmen: FileNamesMissingError,, InputFileMissingErrorund OutputDirectoryErrordas wollte ich mit einer Aussage retten. Ich habe eine andere Ausnahmeklasse namens aufgerufen FileLoadErrorund dann die obigen drei Ausnahmen eingerichtet, um davon zu erben. Ich habe dann nur gerettet FileLoadError.

So was:

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
quelle