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


101

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

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

Я бы хотел как-то сохранить список типов исключений, которые я хочу где-то спасти, и передать эти типы в предложение rescue:

EXCEPTIONS = [FooException, BarException]

а потом:

rescue EXCEPTIONS

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


2
А как насчет спасения * ИСКЛЮЧЕНИЯ?
Роман

Ответы:


199

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

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]

2
Кажется, это работает даже в Ruby 1.8.7. Каков термин для использования символа «*» EXCEPTIONSв этом случае? Хотел бы узнать немного больше.
APB

2
@ Энди Это называется сплат. Обычно это приводит к разложению массива на объекты, разделенные запятыми. При использовании в аргументе, принимающем позицию определения метода, он делает наоборот: объединяет аргументы в массив. Это весьма полезно в различных случаях. Приятно знать, что он работает с 1.8.7. Я соответствующим образом отредактировал свой ответ.
sawa

21
Обратите внимание: если вы хотите получить доступ к экземпляру исключения, используйте этот синтаксис: rescue InvalidRequestError, CardError => e(см. Mikeferrier.com/2012/05/19/… )
Питер Эрлих

1
Этот синтаксис отлично работает:, rescue *EXCEPTIONS => eгде EXCEPTIONS- массив имен классов исключений.
AKS

3

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

Как следует из комментария Питера Эрлиха (указывая на старый пост в блоге Майка Ферриера ), 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'

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


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.