在 Ruby 中捕获异常后重新引发(相同的异常)
Posted
技术标签:
【中文标题】在 Ruby 中捕获异常后重新引发(相同的异常)【英文标题】:Reraise (same exception) after catching an exception in Ruby 【发布时间】:2014-07-09 08:55:20 【问题描述】:我正在尝试通过捕获异常来提高我的 Ruby 技能。我想知道当您有多个方法调用时重新引发相同类型的异常是否很常见。那么,下面的代码有意义吗?是否可以重新引发相同类型的异常,还是我不应该在 process 方法上捕获它?
class Logo
def process
begin
@processed_logo = LogoProcessor::create_image(self.src)
rescue CustomException
raise CustomException
end
end
end
module LogoProcessor
def self.create_image
raise CustomException if some_condition
end
end
【问题讨论】:
【参考方案1】:有时我们只想知道错误发生,而不需要实际处理错误。
通常情况下,负责处理错误的是对象的用户:调用者。如果我们对错误感兴趣,但不想承担这个责任怎么办?我们挽救错误,做我们需要做的任何事情,然后将信号向上传播到堆栈中,就好像什么都没发生一样。
例如,如果我们想记录错误消息,然后让调用者处理呢?
begin
this_will_fail!
rescue Failure => error
log.error error.message
raise
end
不带任何参数调用raise
将引发最后一个错误。在我们的例子中,我们正在重新加注error
。
在您在问题中提出的示例中,根本没有必要重新提出错误。您可以简单地让它自然地向上传播。您的示例中唯一的区别是您正在创建一个新的错误对象并提升它,而不是重新提升最后一个。
【讨论】:
这会从原始异常中丢失stacktrace
,您可能想要包含异常cause
,它在ruby > 2.1中可用
@bjhaid 以这个答案的方式调用raise
完全保留了原始异常,包括backtrace
。 cause
不适用于这种情况。相反,它会在 rescue
块引发新异常时自动填充。
@HommerSmith :如果 raise 之前的行(在这种情况下为 log.error,但它可能是任何东西)失败了怎么办?我正在考虑“确保”它,但是,在确保我需要使用对错误的引用作为“raise”的参数。你怎么看?
@RafałCieślak 每次出现错误时,都会将其分配给 $!
全局变量。不带参数调用raise
会引发$!
中包含的错误,从而有效地引发最后一个错误。但是,raise error
将引发包含在 error
局部变量中的错误,该变量可能与 $!
中包含的对象相同,也可能不同。在我的示例中,$!
与 error
相同。但是,也可以这样做:error = Exception.new; raise error
@RafałCieślak 它不像Every time an error is raised, it is assigned to the $! global variable
那么简单;我发布了完整的解释here【参考方案2】:
这将引发与原始错误相同类型的错误,但您可以自定义消息。
rescue StandardError => e
raise e.class, "Message: #e.message"
【讨论】:
我建议捕获 StandardError 是个坏主意,因为这包括所有类型的低级函数并且可能会挂起您的程序。 我认为这是规则的“例外”,因为我们会立即再次提出例外。 我知道你在那里做了什么,@FreePender ;) 我知道已经过去了一年,但捕捉 StandardError 是安全的,@PaulWhitehead - 这是你永远不能捕捉的异常。 (快速来源:thoughtbot.com/blog/rescue-standarderror-not-exception) 实际上Exception
类有一个exception
方法可以做到这一点。看我的回答。【参考方案3】:
我和评论线程here有同样的问题,即What if the line before (re)raise fails?
我的理解受限于缺少$!
的全局变量是“有点垃圾收集”//“作用于其功能上下文”的知识,下面的示例演示了这一点:
def func
begin
raise StandardError, 'func!'
rescue StandardError => err
puts "$! = #$!.inspect"
end
end
begin
raise StandardError, 'oh no!'
rescue StandardError => err
func
puts "$! = #$!.inspect"
raise
end
上面的输出是:
$! = #<StandardError: func!>
$! = #<StandardError: oh no!>
StandardError: oh no!
from (pry):47:in `__pry__'
这种行为不同于 Python 的 (re)raise
的工作方式。
The documentation for Exception
状态:
当异常已引发但尚未处理时(在
rescue
,ensure
、at_exit
和END
块),设置了两个全局变量:$!
包含当前异常。$@
包含它的回溯。
所以这些变量不是真正的全局变量,它们只在处理错误的块内定义。
begin
raise
rescue
p $! # StandardError
end
p $! # nil
【讨论】:
你是对的!事实证明$!
和$@
不是真正的全局变量。它们仅在 rescue
、ensure
、at_exit
和 END
块中定义。谢谢你今天教了我一些东西!【参考方案4】:
与 FreePender 做同样事情的稍微好一点的方法是使用 Exception
类中的 exception
方法,它是任何错误类的祖先类,如 StandardError
,以便该方法可用到任何错误类。
您可以在 ApiDock 上找到该方法的文档:
没有参数,或者参数与接收者相同,返回接收者。否则,创建一个与接收者相同类的新异常对象,但消息等于 string.to_str。
现在让我们看看它是如何工作的:
begin
this_will_fail!
rescue Failure => error
raise error.exception("Message: #error.message")
end
【讨论】:
以上是关于在 Ruby 中捕获异常后重新引发(相同的异常)的主要内容,如果未能解决你的问题,请参考以下文章