在 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 完全保留了原始异常,包括backtracecause 不适用于这种情况。相反,它会在 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 状态:

当异常已引发但尚未处理时(在rescueensureat_exitEND 块),设置了两个全局变量:

$! 包含当前异常。 $@ 包含它的回溯。

所以这些变量不是真正的全局变量,它们只在处理错误的块内定义。

begin
  raise
rescue
  p $!  # StandardError
end

p $!    # nil

【讨论】:

你是对的!事实证明$!$@ 不是真正的全局变量。它们仅在 rescueensureat_exitEND 块中定义。谢谢你今天教了我一些东西!【参考方案4】:

与 FreePender 做同样事情的稍微好一点的方法是使用 Exception 类中的 exception 方法,它是任何错误类的祖先类,如 StandardError,以便该方法可用到任何错误类。

您可以在 ApiDock 上找到该方法的文档:

没有参数,或者参数与接收者相同,返回接收者。否则,创建一个与接收者相同类的新异常对象,但消息等于 string.to_str。

现在让我们看看它是如何工作的:

begin
  this_will_fail!
rescue Failure => error
  raise error.exception("Message: #error.message")
end

【讨论】:

以上是关于在 Ruby 中捕获异常后重新引发(相同的异常)的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SQL Server 中重新引发相同的异常

如果在生成 std​​::thread 后引发异常,则不会捕获异常

Ruby Rspec 对捕获的异常的消息期望

前端捕获异常技巧总结

使用不同的类型和消息重新引发异常,保留现有信息

您可以在不同的线程上重新引发 .NET 异常吗?