为什么在Ruby中`拯救Exception => e`是不好的风格?

瑞安戴维斯的红宝石QuickRef说(没有解释):

不要拯救例外。 EVER。 否则我会刺伤你。

为什么不? 什么是正确的做法?


TL; DR :使用StandardError来代替一般异常捕获。 当原始异常被重新提出时(例如,当救援只记录异常时),拯救Exception可能没问题。


Exception是Ruby异常层次的根源,所以当你rescue Exception时,你可以从一切中解救出来,包括诸如SyntaxErrorLoadErrorInterrupt类的子类。

救援Interrupt阻止用户使用CTRLC退出程序。

拯救SignalException阻止程序正确响应信号。 除了kill -9之外,它将不可驱动。

拯救SyntaxError意味着失败的eval会默默地执行。

所有这些都可以通过运行这个程序来显示,并尝试CTRLC或kill它:

loop do
  begin
    sleep 1
    eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
  rescue Exception
    puts "I refuse to fail or be stopped!"
  end
end

Exception救援甚至不是默认值。 干

begin
  # iceberg!
rescue
  # lifeboats
end

不从Exception解救,它从StandardError解救出来。 您通常应该指定比默认的StandardError更具体的内容,但从Exception拯救的范围扩大了范围而不是缩小范围,并且可能产生灾难性的结果并使查找错误变得非常困难。


如果您有一种情况需要从StandardError解救出来,并且需要一个具有例外的变量,则可以使用以下格式:

begin
  # iceberg!
rescue => e
  # lifeboats
end

相当于:

begin
  # iceberg!
rescue StandardError => e
  # lifeboats
end

为了记录/报告的目的,从Exception解救出来的少数几个常见情况之一就是,在这种情况下,您应该立即重新提出异常:

begin
  # iceberg?
rescue Exception => e
  # do some logging
  raise e  # not enough lifeboats ;)
end

真正的规则是:不要抛弃异常。 你引用的作者的客观性是有问题的,正如它以结尾的事实所证明的那样

否则我会刺伤你

当然,请注意,信号(默认情况下)会引发异常,通常情况下,长时间运行的进程会通过信号终止,因此捕获异常并且不会终止信号异常会使您的程序非常难以停止。 所以不要这样做:

#! /usr/bin/ruby

while true do
  begin
    line = STDIN.gets
    # heavy processing
  rescue Exception => e
    puts "caught exception #{e}! ohnoes!"
  end
end

不,真的,不要这样做。 甚至不要运行它来查看它是否有效。

但是,假设你有一个线程服务器,并且你希望所有的例外都不是:

  • 被忽略(默认)
  • 停止服务器(如果你说thread.abort_on_exception = true ,会发生这种情况)。
  • 那么这在连接处理线程中是完全可以接受的:

    begin
      # do stuff
    rescue Exception => e
      myLogger.error("uncaught #{e} exception while handling connection: #{e.message}")
        myLogger.error("Stack trace: #{backtrace.map {|l| "  #{l}n"}.join}")
    end
    

    上面的例子演示了Ruby的默认异常处理程序的一个变体,其优点是它不会同时终止程序。 Rails在请求处理程序中执行此操作。

    信号异常在主线程中引发。 后台线程不会得到它们,所以在那里尝试捕获它们没有意义。

    这在生产环境中特别有用,您不希望程序在出现问题时停止。 然后,您可以在日志中进行堆栈转储,并添加到您的代码中,以更优雅的方式处理调用链中的特定异常。

    还要注意,还有另一个Ruby成语,它具有很多相同的效果:

    a = do_something rescue "something else"
    

    在这一行中,如果do_something引发异常,它会被Ruby捕获,抛弃,并且a被分配"something else"

    一般来说,不要这样做,除非您知道您不必担心的特殊情况除外。 一个例子:

    debugger rescue nil
    

    debugger函数是在代码中设置断点的一种非常好的方式,但如果在调试器和Rails外部运行,则会引发异常。 现在理论上你不应该在你的程序中留下调试代码(pff!没有人这样做!),但是由于某种原因,你可能想保留它一段时间,但不能持续运行你的调试器。

    注意:

  • 如果你运行了别人的程序来捕获信号异常并忽略它们(例如上面的代码),那么:

  • 在Linux中,在shell中,键入pgrep rubyps | grep ruby ps | grep ruby ,查找你的违规程序的PID,然后运行kill -9 <PID>
  • 在Windows中,使用任务管理器(CTRL-SHIFT-ESC),转到“进程”选项卡,找到您的进程,右键单击它并选择“结束进程”。
  • 如果您正在与其他人的程序一起工作,无论出于何种原因,这些程序都与这些忽略异常块配合使用,那么将其放在主线的顶端就是一个可能的副本:

    %W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
    

    这会导致程序立即终止,绕过异常处理程序而无需清理,从而响应正常的终止信号。 因此可能会导致数据丢失或类似情况。 小心!

  • 如果你需要这样做:

    begin
      do_something
    rescue Exception => e
      critical_cleanup
      raise
    end
    

    你可以这样做:

    begin
      do_something
    ensure
      critical_cleanup
    end
    

    在第二种情况下,无论是否引发异常,每次都会调用critical cleanup


  • 因为这会捕获所有例外情况。 你的程序不可能从其中任何一个中恢复。

    您应该只处理您知道如何从中恢复的异常。 如果您没有预料到某种异常,请不要处理它,大声崩溃(将详细信息写入日志),然后诊断日志并修复代码。

    吞咽异常是不好的,不要这样做。

    链接地址: http://www.djcxy.com/p/647.html

    上一篇: Why is it bad style to `rescue Exception => e` in Ruby?

    下一篇: accessor in Ruby?