Передача декількох класів помилок до рятувального пункту рубіну у сухий спосіб


100

У мене є код, який потрібно врятувати кілька винятків у рубіні:

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

Що я хотів би зробити, це якимось чином зберігати список типів винятків, які я хочу десь врятувати, і передавати ці типи в рятувальний пункт:

EXCEPTIONS = [FooException, BarException]

і потім:

rescue EXCEPTIONS

Це навіть можливо, і чи можливо без по-справжньому хак-у дзвінків eval? Я не сподіваюся, враховуючи, що я бачу, TypeError: class or module required for rescue clauseколи я намагаюся сказати вище.


2
Що з порятунком * ВИСНОВКИ?
Роман

Відповіді:


197

Ви можете використовувати масив з оператором splat *.

EXCEPTIONS = [FooException, BarException]

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

Якщо ви збираєтесь використовувати константу для масиву, як зазначено вище (з EXCEPTIONS), зауважте, що ви не можете визначити його в рамках визначення, а також якщо ви визначите його в іншому класі, вам доведеться посилатися на нього з його простором імен. Власне, це не повинно бути постійним.


Splat Operator

Оператор splat *"розпаковує" масив у своєму положенні так, що

rescue *EXCEPTIONS

означає те саме, що

rescue FooException, BarException

Ви також можете використовувати його в прямому масиві як

[BazException, *EXCEPTIONS, BangExcepion]

що те саме

[BazException, FooException, BarException, BangExcepion]

або в позиції аргументу

method(BazException, *EXCEPTIONS, BangExcepion)

що означає

method(BazException, FooException, BarException, BangExcepion)

[] розширюється до вакууму:

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

Одна різниця між рубіном 1,8 і рубіном 1,9 є в nil.

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

Будьте обережні з об'єктами, на яких to_aвизначено, що to_aбуде застосовано в таких випадках:

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

З іншими типами об’єктів вона повертається сама.

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

2
Схоже, це працює навіть у рубіні 1.8.7. Який термін використання символу "*" перед EXCEPTIONSцим? Хочеться дізнатися трохи більше.
APB

2
@Andy Це називається splat. Зазвичай це призводить до розкладання масиву на розділені комами об’єкти. Якщо використовується в позиції прийому аргументу визначення методу, він робить інший спосіб: об'єднайте аргументи в масив. Це досить корисно в різних випадках. Добре знати, що він працює з 1.8.7. Я відповідно відредагував свою відповідь.
sawa

20
Зауважте, що якщо ви хочете отримати доступ до екземпляра винятку, використовуйте цей синтаксис: rescue InvalidRequestError, CardError => e(див. Mikeferrier.com/2012/05/19/… )
Peter Ehrlich

Цей синтаксис працює чудово:, rescue *EXCEPTIONS => eде EXCEPTIONSє масив імен класів виключень.
акс

3

Хоча відповідь, надана @sawa, технічно правильна, я думаю, що вона зловживає механізмом поводження з винятками Рубі.

Як підказує коментар Пітера Ерліха (вказуючи на стару публікацію в блозі Майка Фер’є ), Рубі вже оснащений механізмом обробці винятків 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'

Використовуючи цю техніку, ми можемо отримати доступ до об’єкта винятку, який зазвичай містить в собі якусь цінну інформацію.


1

Я просто зіткнувся з цим питанням і знайшов альтернативне рішення. У випадку, якщо ваші FooExceptionі BarExceptionвсі будуть типовими класами виключень, і особливо, якщо вони є тематично пов'язаними, ви можете структурувати свою ієрархію спадкування таким чином, що всі вони успадковуватимуться з одного батьківського класу, а потім врятуватимуть лише батьківський клас.

Наприклад , у мене було три винятки: FileNamesMissingError, InputFileMissingErrorі OutputDirectoryErrorщо я хотів врятувати з допомогою одного оператора. Я зробив ще один клас винятків під назвою, FileLoadErrorа потім встановив вищевказані три винятки для успадкування від нього. Тоді я врятував лише FileLoadError.

Подобається це:

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.