问题
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.
回答1:
You can use an array with the splat operator *
.
EXCEPTIONS = [FooException, BarException]
begin
a = rand
if a > 0.5
raise FooException
else
raise BarException
end
rescue *EXCEPTIONS
puts "rescued!"
end
If you are going to use a constant for the array as above (with EXCEPTIONS
), note that you cannot define it within a definition, and also if you define it in some other class, you have to refer to it with its namespace. Actually, it does not have to be a constant.
Splat Operator
The splat operator *
"unpacks" an array in its position so that
rescue *EXCEPTIONS
means the same as
rescue FooException, BarException
You can also use it within an array literal as
[BazException, *EXCEPTIONS, BangExcepion]
which is the same as
[BazException, FooException, BarException, BangExcepion]
or in an argument position
method(BazException, *EXCEPTIONS, BangExcepion)
which means
method(BazException, FooException, BarException, BangExcepion)
[]
expands to vacuity:
[a, *[], b] # => [a, b]
One difference between ruby 1.8 and ruby 1.9 is with nil
.
[a, *nil, b] # => [a, b] (ruby 1.9)
[a, *nil, b] # => [a, nil, b] (ruby 1.8)
Be careful with objects on which to_a
is defined, as to_a
will be applied in such cases:
[a, *{k: :v}, b] # => [a, [:k, :v], b]
With other types of objects, it returns itself.
[1, *2, 3] # => [1, 2, 3]
回答2:
I just ran into this issue and found an alternate solution. In the case your FooException
and BarException
are all going to be custom exception classes and particularly if they are all thematically related, you can structure your inheritance hierarchy such that they will all inherit from the same parent class and then rescue only the parent class.
For example I had three exceptions: FileNamesMissingError
,InputFileMissingError
, and OutputDirectoryError
that I wanted to rescue with one statement. I made another exception class called FileLoadError
and then set up the above three exceptions to inherit from it. I then rescued only FileLoadError
.
Like this:
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
回答3:
While the answer given by @sawa is technically right, I think it misuses Ruby's exception handling mechanism.
As the comment by Peter Ehrlich suggests (by pointing to an old blog post by Mike Ferrier), Ruby is already equipped with a DRY exception handler mechanism:
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'
By using this technique, we can access the exception object, which usually has some valuable information in it.
来源:https://stackoverflow.com/questions/5781639/passing-multiple-error-classes-to-rubys-rescue-clause-in-a-dry-fashion