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

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

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

为什么不? 什么是正确的事情?

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

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

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

拯救SyntaxError意味着那个失败的eval会静静地做。

所有这些都可以通过运行这个程序来显示,并尝试按CTRL C或者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解救出来,并且需要一个具有例外的variables,则可以使用以下forms:

 begin # iceberg! rescue => e # lifeboats end 

相当于:

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

为了logging/报告的目的,从Exception解救出来的less数几个常见情况之一是,在这种情况下,你应该立即重新提出exception:

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

真正的规则是:不要抛弃exception。 你引用的作者的客观性是值得怀疑的,正如它以它结束的事实所certificate的那样

否则我会刺伤你

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

 #! /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的默认exception处理程序的一个变体,其优点是它不会杀死你的程序。 Rails在请求处理程序中执行此操作。

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

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

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

 a = do_something rescue "something else" 

在这一行中,如果do_something引发了一个exception,它会被Ruby抓到,被抛弃,并被赋予"something else"

一般来说,除非你知道你不需要担心,否则不要这样做。 一个例子:

 debugger rescue nil 

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

注意:

  1. 如果你运行了别人的程序来捕获信号exception并忽略它们(比如上面的代码),那么:

    • 在Linux中,在shell中inputpgrep rubyps | grep ruby ps | grep ruby ,查找你的违规程序的PID,然后运行kill -9 <PID>
    • 在Windows中,使用任务pipe理器( CTRLSHIFTESC ),进入“进程”选项卡,find你的进程,右键单击它并select“结束进程”。
  2. 如果你正在与别人的程序一起工作,这个程序不pipe出于何种原因都与这些忽略exception块组合在一起,那么把它放在主线的顶端是一个可能的指令:

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

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

  3. 如果你需要这样做:

     begin do_something rescue Exception => e critical_cleanup raise end 

    你可以这样做:

     begin do_something ensure critical_cleanup end 

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

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

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

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

假设你在汽车里(运行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 

哔哔

警告:捕获了SyntaxErrorexception。

信息:logging错误 – 持续进程。

你注意到有什么地方是错误的,并且你紧急中断( ^CInterrupt

哔哔

警告:捕获中断exception。

信息:logging错误 – 持续进程。

是的 – 这并没有太大的帮助。 你非常靠近铁路,所以你把车停在公园( killSignalException )。

哔哔

警告:捕获到的SignalExceptionexception。

信息:logging错误 – 持续进程。

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

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


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

假设你是那辆车,并且如果在停车前车速超过5英里/小时,你想确保安全气囊膨胀。

  begin # do driving stuff rescue Exception => e self.airbags.inflate if self.speed >= 5.mph raise end 

这是规则的例外情况: 只有当您重新抛出exception时才能捕获Exception 。 所以,更好的规则是永远不要吞噬Exception ,并总是重新提出错误。

但是在Ruby这样的语言中添加救援function是很容易忘记的,在重新提出问题之前提出救援声明会让人觉得有点不干净。 而且你不想忘记raise声明。 如果你这样做,祝你好运,试图find这个错误。

谢天谢地,Ruby很棒,你可以使用ensure代码运行的ensure关键字。 无论如何,如果一个exception被抛出,那么ensure关键字将运行代码,唯一的例外是世界结束(或其他不太可能发生的事件)。

  begin # do driving stuff ensure self.airbags.inflate if self.speed >= 5.mph end 

繁荣! 而且该代码应该运行。 你应该使用rescue Exception => e的唯一原因rescue Exception => e是,如果你需要访问exception,或者如果你只想让代码运行在一个exception。 并记得重新提出错误。 每次。 或者你会有3人刺你(包括你的老板)。


TL; DR

不要rescue Exception => e (而不是重新提出exception) – 否则你可能会开车过桥。

这是一个规则的特定情况,你不应该捕捉任何你不知道如何处理的exception。 如果你不知道如何处理它,最好让系统的其他部分抓住并处理它。

这也将隐藏你的错误,例如,如果你打错方法名称:

 def my_fun "my_fun" end begin # you mistypped my_fun to my_func my_func # my_func() rescue Exception # rescued NameError (or NoMethodError if you called method with parenthesis) end