Передача нескольких классов ошибок в предложение спасения ruby ​​СУХИМ способом

У меня есть код, который должен спасать несколько типов исключений в ruby:

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, когда пытаюсь сделать это выше.


person apb    schedule 25.04.2011    source источник
comment
Как насчет спасения *ИСКЛЮЧЕНИЯ ?   -  person Roman    schedule 26.04.2011


Ответы (3)


Вы можете использовать массив с оператором знака знака *.

EXCEPTIONS = [FooException, BarException]

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

Если вы собираетесь использовать константу для массива, как указано выше (с EXCEPTIONS), обратите внимание, что вы не можете определить ее в определении, а также, если вы определяете ее в каком-то другом классе, вы должны ссылаться на нее с ее пространством имен. На самом деле, это не должно быть константой.


Оператор Splat

Оператор 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]

Одно различие между ruby ​​1.8 и ruby ​​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]
person sawa    schedule 25.04.2011
comment
Похоже, это работает даже в ruby ​​1.8.7. Что означает использование символа '*' перед EXCEPTIONS в этом случае? Хотел бы узнать немного больше. - person apb; 26.04.2011
comment
@Энди Это называется сплат. Обычно это приводит к разложению массива на объекты, разделенные запятыми. При использовании в позиции получения аргумента определения метода происходит наоборот: объединяются аргументы в массив. Это весьма полезно в различных случаях. Приятно знать, что он работает с 1.8.7. Я отредактировал свой ответ соответственно. - person sawa; 26.04.2011
comment
Обратите внимание: если вы хотите получить доступ к экземпляру исключения, используйте следующий синтаксис: rescue InvalidRequestError, CardError => e (см. mikeferrier.com/2012/05/19/) - person Peter Ehrlich; 11.10.2012
comment
Этот синтаксис прекрасно работает: rescue *EXCEPTIONS => e, где EXCEPTIONS — массив имен классов исключений. - person aks; 19.02.2020

Изменить/обновить

Я упустил весь смысл первоначального вопроса.
Хотя принятый ответ является допустимым, на мой взгляд, использовать предложенную технику не рекомендуется. Всегда можно иметь функцию упаковки с желаемым (и общим) try/rescue.


Хотя ответ, данный @sawa, технически верен, я думаю, что он неправильно использует механизм обработки исключений Ruby.

Как предполагает комментарий Peter Ehrlich (указав на старый файл запись в блоге Майка Феррье), Ruby уже оснащен механизмом обработки исключений 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'

Используя эту технику, мы можем получить доступ к объекту исключения, который обычно содержит некоторую ценную информацию.

person Ron Klein    schedule 05.11.2019
comment
-1 Это синтаксис ОП в его вопросе. Он хочет извлечь этот список в место, где его легче поддерживать, я полагаю, потому что один и тот же список типов исключений должен повторяться в нескольких местах, а обновления списка были подвержены ошибкам. - person Segfault; 02.03.2021
comment
@Segfault только после прочтения вашего комментария я наконец понял вопрос ОП .. - person Ron Klein; 03.03.2021

Я только что столкнулся с этой проблемой и нашел альтернативное решение. В случае, если все ваши 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
person Mikhail Golubitsky    schedule 15.06.2015