解决方案:
class FooError < StandardError
attr_reader :foo
def initialize(foo)
super
@foo = foo
end
end
如果您遵循Rubocop Style Guide 并始终将您的消息作为第二个参数传递给raise,这是最好的方法:
raise FooError.new('foo'), 'bar'
你可以像这样得到foo:
rescue FooError => error
error.foo # => 'foo'
error.message # => 'bar'
如果你想自定义错误信息然后写:
class FooError < StandardError
attr_reader :foo
def initialize(foo)
super
@foo = foo
end
def message
"The foo is: #{foo}"
end
end
如果需要foo,这很有效。如果您希望 foo 成为可选参数,请继续阅读。
说明:
将您的消息作为第二个参数传递给raise
正如Rubocop Style Guide 所说,消息和异常类应该作为单独的参数提供,因为如果你写:
raise FooError.new('bar')
又想给raise传一个回溯,不传两次消息就没办法了:
raise FooError.new('bar'), 'bar', other_error.backtrace
正如this answer 所说,如果您想将异常重新引发为具有相同回溯和不同消息或数据的新实例,则需要传递回溯。
实现FooError
问题的症结在于,如果foo是可选参数,那么引发异常有两种不同的方式:
raise FooError.new('foo'), 'bar', backtrace # case 1
和
raise FooError, 'bar', backtrace # case 2
我们希望FooError 与两者一起工作。
在情况 1 中,由于您提供的是错误实例而不是类,raise 将 'bar' 设置为错误实例的消息。
情况 2,raise 为您实例化 FooError 并将 'bar' 作为唯一参数传递,但它不会像情况 1 那样在初始化后设置消息。消息,您必须在 FooError#initialize 中调用 super,并将消息作为唯一参数。
所以在情况 1 中,FooError#initialize 接收到 'foo',在情况 2 中,它接收到 'bar'。它已超载,通常无法区分这些情况。这是 Ruby 的设计缺陷。所以如果foo 是一个可选参数,你有三个选择:
(a) 接受传递给FooError#initialize 的值可以是foo 或消息。
(b) 仅将 case 1 或 case 2 样式与 raise 一起使用,但不能同时使用两者。
(c) 将foo 设为关键字参数。
如果您不希望 foo 成为关键字参数,我建议 (a) 并且我上面的 FooError 实现旨在以这种方式工作。
如果你 raise 和 FooError 使用 case 2 样式,foo 的值就是消息,它被隐式传递给 super。如果您向FooError#initialize 添加更多参数,您将需要一个明确的super(foo)。
如果您使用关键字参数 (h/t Lemon Cat's answer),则代码如下所示:
class FooError < StandardError
attr_reader :foo
def initialize(message, foo: nil)
super(message)
@foo = foo
end
end
加注看起来像:
raise FooError, 'bar', backtrace
raise FooError(foo: 'foo'), 'bar', backtrace