Ruby 为什么“拯救例外=>”是一种糟糕的风格;用红宝石做的?

Ruby 为什么“拯救例外=>”是一种糟糕的风格;用红宝石做的?,ruby,exception-handling,Ruby,Exception Handling,Ryan Davis说(没有解释): 不要拯救例外。曾经否则我会刺你 为什么不呢?正确的做法是什么?因为这样可以捕获所有异常。您的程序不太可能从任何中恢复 您应该只处理您知道如何从中恢复的异常。如果您没有预料到某种异常,请不要处理它,大声崩溃(将详细信息写入日志),然后诊断日志并修复代码 吞咽异常不好,不要这样做。TL;DR:使用标准错误代替常规异常捕获。重新引发原始异常时(例如,当救援以仅记录异常时),救援异常可能没问题 Exception是的根,因此当您rescue Exception时,

Ryan Davis说(没有解释):

不要拯救例外。曾经否则我会刺你


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

因为这样可以捕获所有异常。您的程序不太可能从任何中恢复

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


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

TL;DR:使用
标准错误
代替常规异常捕获。重新引发原始异常时(例如,当救援以仅记录异常时),救援
异常
可能没问题


Exception
是的根,因此当您
rescue Exception
时,您可以从所有内容中进行解救,包括
SyntaxError
LoadError
Interrupt
等子类

救援
中断
可防止用户使用CTRLC退出程序

救援
SignalException
会阻止程序正确响应信号。除非通过
kill-9
,否则它是不可修改的

拯救
SyntaxError
意味着失败的
eval
s将以静默方式进行

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

loop do
  begin
    sleep 1
    eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
  rescue Exception
    puts "I refuse to fail or be stopped!"
  end
end
异常中营救甚至不是默认设置。做

begin
  # iceberg!
rescue
  # lifeboats
end
不从
异常中救援
,它从
标准错误中救援。通常,您应该指定比默认的
StandardError
更具体的内容,但是从
异常中营救
会扩大范围,而不是缩小范围,并且可能会产生灾难性的结果,使错误查找变得极其困难


如果您确实希望从
StandardError
中解救,并且您需要一个例外变量,则可以使用以下表格:

begin
  # iceberg!
rescue => e
  # lifeboats
end
这相当于:

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

异常中营救正常的少数常见情况之一是出于日志记录/报告目的,在这种情况下,您应该立即重新引发异常:

begin
  # iceberg?
rescue Exception => e
  # do some logging
  raise # 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\u on\u 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"
    
    在这一行中,如果
    dou\u something
    引发异常,它将被Ruby捕获并丢弃,并且
    a
    被指定为
    “其他东西”

    一般来说,不要这样做,除非在特殊情况下,你知道你不需要担心。一个例子:

    debugger rescue nil
    
    debugger
    函数是在代码中设置断点的一种非常好的方法,但是如果在调试器和Rails之外运行,则会引发异常。从理论上讲,您不应该将调试代码留在程序中(pff!没有人这样做!),但出于某种原因,您可能希望将其保留一段时间,但不要继续运行调试程序

    注:

  • 如果您运行了其他人的程序,该程序捕获信号异常并忽略它们(如上面的代码所示),则:

    • 在Linux中,在shell中键入
      pgrep ruby
      ,或
      ps | grep ruby
      ,查找有问题的程序的PID,然后运行
      kill-9
    • 在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
    
    在第二种情况下,无论是否引发异常,每次都将调用
  • 关键清理
  • TL;博士 不要
    rescue Exception=>e
    (不要重新引发异常)-否则您可能会开车离开驾驶台


    假设你是
    def turn_left
      self.turn left:
    end
    
    begin
      #...
      eval self.steering_wheel
      #...
    rescue Exception => e
      self.beep
      self.log "Caught #{e}.", :warn
      self.log "Logged Error - Continuing Process.", :info
    end
    
     begin 
        # do driving stuff
     rescue Exception => e
        self.airbags.inflate if self.exceeding_safe_stopping_momentum?
        raise
     end
    
     begin 
        # do driving stuff
     ensure
        self.airbags.inflate if self.exceeding_safe_stopping_momentum?
     end