首页 文章

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

提问于
浏览
822

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

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

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

5 回答

  • 44

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


    ExceptionRuby's exception hierarchy的根,所以当你__63839_时,你可以从所有东西中解救出来,包括诸如 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 开始拯救范围而不是缩小范围,并且可能产生灾难性的结果并使得捕获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
    
  • 76

    那个's a specific case of the rule that you shouldn't会 grab 你不知道如何处理它的任何异常,让系统的其他部分 grab 并处理它总是更好 .

  • 1275

    真正的规则是:不要抛弃异常 . 你引用的作者的客观性是值得怀疑的,正如它的结尾所证明的那样

    或者我会刺伤你

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

    #! /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 ,查找违规程序的PID,然后运行 kill -9 <PID> .
      在Windows中

    • ,使用任务管理器(CTRL-SHIFT-ESC),转到"processes"选项卡,找到您的进程,右键单击它并选择"End process" .

    • 如果您正在使用其他人的程序,无论出于何种原因,这些程序都会使用这些忽略异常块,那么将其放在主线的顶部就是一个可能的问题:

    %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 ,无论是否抛出异常 .

  • 53

    因为这会捕获所有异常 . 你的程序不太可能从 any 中恢复 .

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

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

  • 9

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

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

    def turn_left
      self.turn left:
    end
    

    哎呀!这可能不是很好,幸运的是,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
    

    beep beep警告:Caught SyntaxError异常 . 信息:记录错误 - 继续过程 .

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

    蜂鸣声警告:捕获中断异常 . 信息:记录错误 - 继续过程 .

    是的 - 那没有't help much. You'非常接近铁路,所以你把车停在公园( 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
    

    这是规则的例外:你可以 grab Exception only if you re-raise the exception . 所以,更好的规则是永远不要吞下 Exception ,并且总是重新引发错误 .

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

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

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

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

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


    TL; DR

    不要 rescue Exception => e (而不是重新提高异常) - 或者你可能会开出一座桥 .

相关问题