Przekazywanie wielu klas błędów do klauzuli ratunkowej ruby ​​w sposób SUCHY


100

Mam kod, który musi uratować wiele typów wyjątków w Rubim:

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

Chciałbym w jakiś sposób zapisać listę typów wyjątków, które chcę gdzieś uratować i przekazać je do klauzuli ratunkowej:

EXCEPTIONS = [FooException, BarException]

i wtedy:

rescue EXCEPTIONS

Czy jest to w ogóle możliwe i czy jest to możliwe bez niektórych naprawdę hakerskich połączeń do eval? Nie mam nadziei, biorąc pod uwagę, że widzę, TypeError: class or module required for rescue clausekiedy próbuję wykonać powyższe.


2
A co z ratowaniem * WYJĄTKÓW?
Roman,

Odpowiedzi:


197

Możesz użyć tablicy z operatorem splat *.

EXCEPTIONS = [FooException, BarException]

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

Jeśli zamierzasz użyć stałej dla tablicy jak powyżej (z EXCEPTIONS), zwróć uwagę, że nie możesz jej zdefiniować w definicji, a także jeśli zdefiniujesz ją w innej klasie, musisz odwołać się do niej za pomocą jej przestrzeni nazw. Właściwie nie musi to być stała.


Operator Splat

Operator splat *"rozpakowuje" tablicę w jej pozycji, tak że

rescue *EXCEPTIONS

oznacza to samo co

rescue FooException, BarException

Można go również użyć w literale tablicowym jako

[BazException, *EXCEPTIONS, BangExcepion]

który jest taki sam jak

[BazException, FooException, BarException, BangExcepion]

lub w pozycji argumentu

method(BazException, *EXCEPTIONS, BangExcepion)

co znaczy

method(BazException, FooException, BarException, BangExcepion)

[] rozszerza się do wakatu:

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

Jedna różnica między ruby ​​1,8 a ruby ​​1,9 polega na nil.

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

Uważaj na obiekty, na których to_ajest zdefiniowany, co to_azostanie zastosowane w takich przypadkach:

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

W przypadku innych typów obiektów powraca.

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

2
Wygląda na to, że działa nawet w Ruby 1.8.7. Jaki jest termin używania znaku „*” na początku EXCEPTIONStego przypadku? Chciałbym dowiedzieć się trochę więcej.
apb

2
@Andy Nazywa się splat. Zwykle powoduje rozłożenie tablicy na obiekty oddzielone przecinkami. Gdy jest używany w pozycji otrzymującej argument w definicji metody, robi to w drugą stronę: umieszcza argumenty razem w tablicy. Jest to przydatne przy różnych okazjach. Dobrze wiedzieć, że działa z wersją 1.8.7. Odpowiednio zredagowałem odpowiedź.
sawa

20
Zauważ, że jeśli chcesz uzyskać dostęp do instancji wyjątku, użyj następującej składni: rescue InvalidRequestError, CardError => e(patrz mikeferrier.com/2012/05/19/... )
Peter Ehrlich

Ta składnia działa dobrze:, rescue *EXCEPTIONS => egdzie EXCEPTIONSjest tablicą nazw klas wyjątków.
aks

3

Podczas gdy odpowiedź udzielona przez @sawa jest technicznie poprawna, myślę, że niewłaściwie wykorzystuje mechanizm obsługi wyjątków Rubiego.

Jak sugeruje komentarz Petera Ehrlicha (wskazując na stary post na blogu Mike'a Ferriera ), Ruby jest już wyposażony w mechanizm obsługi wyjątków 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'

Korzystając z tej techniki, możemy uzyskać dostęp do obiektu wyjątku, który zwykle zawiera cenne informacje.


1

Właśnie natknąłem się na ten problem i znalazłem alternatywne rozwiązanie. W przypadku Twojego FooExceptioniBarException gdy wszystkie będą niestandardowymi klasami wyjątków, a zwłaszcza jeśli wszystkie są powiązane tematycznie, możesz tak ustrukturyzować hierarchię dziedziczenia, aby wszystkie odziedziczyły z tej samej klasy nadrzędnej, a następnie uratują tylko klasę nadrzędną.

Na przykład miałem trzy wyjątki: FileNamesMissingError, InputFileMissingError, i OutputDirectoryErrorże chciałem ratunek z jednego rachunku. Wywołałem inną klasę wyjątków, FileLoadErrora następnie ustawiłem powyższe trzy wyjątki, aby dziedziczyć po niej. Wtedy tylko uratowałemFileLoadError .

Lubię to:

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.