Ruby – 为什么在Ruby中“rescue Exception => e`是不好的风格?

Ryan Davis的Ruby QuickRef说(没有解释):

不要rescue Exception。EVER。或者我会刺你。

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


TL; DRStandardError代替一般异常捕获。当重新引发原始异常时(例如,当救援仅记录异常时),抢救Exception可能没问题。


Exception是根Ruby的异常层次结构,所以当你rescue Exception从拯救一切,包括子类,如SyntaxErrorLoadErrorInterrupt

拯救会Interrupt阻止用户CTRLC退出程序。

拯救会SignalException阻止程序正确响应信号。除了之外,它将是不可杀戮的kill -9

拯救SyntaxError意味着eval失败的s会默默地这样做。

所有这些都可以通过运行此程序来显示,并尝试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 扩大范围而不是缩小范围开始,可能会产生灾难性的结果并使得捕获bug非常困难。


如果您确实需要进行救援,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

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

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

  1. 被忽略(默认)
  2. 停止服务器(如果你说,就会发生这种情况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!没有人这样做!)但是你可能因为某些原因想要将它保留一段时间,但不能继续运行你的调试器。

注意:

  1. 如果你运行别人的程序捕获信号异常并忽略它们(比如上面的代码)那么:
    • 在Linux中,在shell中,键入pgrep rubyps | grep ruby查找违规程序的PID,然后运行kill -9 <PID>
    • 在Windows中,使用任务管理器(CTRL– SHIFT– ESC),转到“进程”选项卡,找到您的进程,右键单击它并选择“结束进程”。
  2. 如果您正在使用其他人的程序,无论出于何种原因,这些程序都充满了这些忽略异常块,那么将其放在主线的顶部就是一个可能的问题:
    %W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }

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

  3. 如果你需要这样做:
    begin
      do_something
    rescue Exception => e
      critical_cleanup
      raise
    end

    你实际上可以这样做:

    begin
      do_something
    ensure
      critical_cleanup
    end

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

添加评论

友情链接:蝴蝶教程