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

时间:2012-04-06 19:17:48

标签: ruby exception-handling

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

  

不要救援Exception。 EVER。或者我会刺伤你。

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

6 个答案:

答案 0 :(得分:1316)

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


ExceptionRuby's exception hierarchy的根,所以当您rescue Exception拯救所有内容时,包括SyntaxError,{{等子类1}}和LoadError

拯救Interrupt会阻止用户使用 CTRL C 退出程序。

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

拯救kill -9意味着失败的SyntaxError会默默地这样做。

所有这些都可以通过运行此程序来显示,并尝试 CTRL C eval

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

答案 1 :(得分:79)

真正的规则是:不要丢弃异常。你引用的作者的客观性是值得怀疑的,正如它以

结尾的事实所证明的那样
  

或者我会刺伤你

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

#! /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则会发生这种情况)。
  3. 然后在连接处理线程中完全可以接受:

    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

答案 2 :(得分:60)

让我们说你在车里(运行Ruby)。您最近安装了一个带有无线升级系统(使用eval)的新方向盘,但您并不知道其中一个程序员搞砸了语法。

你在桥上,意识到你正朝着栏杆前进,所以你向左转。

def turn_left
  self.turn left:
end

糟糕!这可能是 Not Good ™,幸运的是,Ruby引发了SyntaxError

汽车应立即停止 - 对吗?

不。

begin
  #...
  eval self.steering_wheel
  #...
rescue Exception => e
  self.beep
  self.log "Caught #{e}.", :warn
  self.log "Logged Error - Continuing Process.", :info
end
  

发出哔哔声

     

警告:Caught SyntaxError异常。

     

信息:记录错误 - 持续过程。

你注意到有些事情是错误的,而且你在紧急休息时间砰的一声(^CInterrupt

  

发出哔哔声

     

警告:捕获中断异常。

     

信息:记录错误 - 持续过程。

是的 - 这没什么帮助。你非常靠近铁轨,所以你把车停在公园里(kill ing:SignalException)。

  

发出哔哔声

     

警告:捕获SignalException异常。

     

信息:记录错误 - 持续过程。

在最后一秒,你拉出钥匙(kill -9),然后汽车停下来,你向前冲进方向盘(安全气囊不能充气,因为你没有优雅地停止程序 - 你终止它了),你车后面的电脑猛然撞到它前面的座位上。半满的可乐可以溢出纸张。背面的杂货被粉碎,大部分都是蛋黄和牛奶。汽车需要严格的维修和清洁。 (数据丢失)

希望你有保险(备份)。哦是的 - 因为安全气囊没有充气,你可能会受伤(被解雇等)。

但是等等!您可能想要使用rescue Exception => e更多原因!

让我们说你是那辆车,如果汽车超过其安全停止动力,你要确保安全气囊膨胀。

 begin 
    # do driving stuff
 rescue Exception => e
    self.airbags.inflate if self.exceeding_safe_stopping_momentum?
    raise
 end

以下是该规则的例外情况:只有在您重新提出异常时才能抓住Exception 。因此,更好的规则是永远不要吞下Exception,并且总是重新提出错误。

但是在像Ruby这样的语言中添加救援很容易被遗忘,并且在重新提出问题之前立即发布救援声明感觉有点不干。并且想忘记raise声明。如果你这样做,祝你好运,找到那个错误。

值得庆幸的是,Ruby非常棒,您只需使用ensure关键字即可确保代码运行。 ensure关键字将运行代码,无论如何 - 如果抛出异常,如果不是,则唯一的例外是世界结束(或其他不太可能发生的事件)。

 begin 
    # do driving stuff
 ensure
    self.airbags.inflate if self.exceeding_safe_stopping_momentum?
 end

轰!并且该代码应该运行。您应该使用rescue Exception => e的唯一原因是您需要访问异常,或者您只希望代码在异常上运行。并记得重新提出错误。每一次。

注意:正如@Niall指出的那样,确保始终运行。这很好,因为有时你的程序会骗你,而不会抛出异常,即使出现问题。对于像气囊充气这样的关键任务,无论如何都要确保它发生。因此,每次停车检查是否抛出异常都是一个好主意。尽管在大多数编程环境中充气安全气囊是一项不常见的任务,但实际上这在大多数清理任务中都很常见。

TL; DR

不要rescue Exception => e(而不是重新提出异常) - 或者可能开出桥梁。

答案 3 :(得分:45)

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

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

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

答案 4 :(得分:9)

这是规则的一个特例,你不应该抓住你不知道如何处理的任何异常。如果您不知道如何处理它,最好让系统的其他部分捕获并处理它。

答案 5 :(得分:0)

我刚刚在honeybadger.io阅读了一篇很棒的博客文章:

Ruby's Exception vs StandardError: What's the difference?

  

为什么不应该拯救异常

     

挽救异常的问题   是它实际上挽救了从中继承的每个异常   例外。那是...所有人!

     

这是一个问题,因为使用了一些例外   由Ruby内部提供。他们与您的应用没有任何关系,并且   吞下它们会导致坏事发生。

     

以下是一些大问题:

     
      
  • SignalException :: Interrupt-如果营救此问题,则无法退出   Ctrl-c来打开应用。

  •   
  • ScriptError :: SyntaxError-吞咽语法错误意味着   像puts(“ Forgot something)一样会失败。

  •   
  • NoMemoryError-想知道当您的程序保留时会发生什么   用完所有RAM后运行?我也不是。

  •   
     

begin
  do_something()
rescue Exception => e
  # Don't do this. This will swallow every single exception. Nothing gets past it. 
end
     

我猜你真的不想吞下任何这些   系统级异常。您只想抓住所有   应用程序级别错误。异常导致了您的代码。

     

幸运的是,有一种简单的方法。