Passing multiple error classes to ruby's rescue clause in a DRY fashion

I have some code that needs to rescue multiple types of exceptions in ruby:

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

What I'd like to do is somehow store the list of exception types that I want to rescue somewhere and pass those types to the rescue clause:

EXCEPTIONS = [FooException, BarException]

and then:

rescue EXCEPTIONS

Is this even possible, and is it possible without some really hack-y calls to eval? I'm not hopeful given that I'm seeing TypeError: class or module required for rescue clause when I attempt the above.

48337 次浏览

可以将数组与分裂操作符 *一起使用。

EXCEPTIONS = [FooException, BarException]


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

如果要像上面那样(使用 EXCEPTIONS)对数组使用常量,请注意不能在定义中定义常量,如果在其他类中定义常量,则必须使用其名称空间引用它。实际上,它不一定是常数。


操作员

The splat operator * "unpacks" an array in its position so that

rescue *EXCEPTIONS

意思与

rescue FooException, BarException

也可以在数组文字中使用它作为

[BazException, *EXCEPTIONS, BangExcepion]

which is the same as

[BazException, FooException, BarException, BangExcepion]

或者在争论的立场

method(BazException, *EXCEPTIONS, BangExcepion)

which means

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]

With other types of objects, it returns itself.

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

我遇到了这个问题,找到了另一个解决方案。在 FooExceptionBarException都是自定义异常类的情况下,特别是如果它们都是主题相关的,那么可以构造继承层次结构,使它们都从同一个父类继承,然后只拯救父类。

例如,我有三个异常: FileNamesMissingErrorInputFileMissingErrorOutputDirectoryError,我想用一个语句来拯救它们。我创建了另一个名为 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

编辑/更新

我没有抓住最初问题的要点。
虽然接受的答案是有效的,但在我看来,使用建议的技术并不是一个好的做法。总是可以使用所需的(和通用的) try/rescue包装函数。


虽然@sawa 给出的 答案在技术上是正确的,但我认为它误用了 Ruby 的异常处理机制。

正如 Peter Ehrlich的评论所暗示的(通过指向一个旧的 Mike Ferrier 的博客文章) ,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'

通过使用这种技术,我们可以访问异常对象,其中通常包含一些有价值的信息。