为什么在Ruby中使用”rescue exception=>e”是不好的样式?

Why is it bad style to `rescue Exception => e` in Ruby?

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

Don’t rescue Exception. EVER. or I will stab you.

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


tl;dr:用StandardError代替一般的异常捕获。当重新引发原始异常时(例如,当救援只记录异常时),救援Exception可能是正常的。

Exception是Ruby异常层次结构的根,所以当你rescue Exception时,你可以从任何东西中拯救出来,包括SyntaxErrorLoadErrorInterrupt等子类。

挽救Interrupt会阻止用户使用ctrlc退出程序。

挽救SignalException会阻止程序正确响应信号。它将是不可破解的,除非是由kill -9

拯救SyntaxError意味着失败的evals将默默地这样做。

所有这些都可以通过运行该程序并尝试ctrlckill来显示:

1
2
3
4
5
6
7
8
loop do
  begin
    sleep 1
    eval"djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
  rescue Exception
    puts"I refuse to fail or be stopped!"
  end
end

Exception中拯救甚至不是违约。做

1
2
3
4
5
begin
  # iceberg!
rescue
  # lifeboats
end

不救Exception,救StandardError。一般来说,您应该指定比默认的StandardError更具体的内容,但是从Exception中进行救援会扩大范围,而不是缩小范围,并且可能会产生灾难性的结果,并使查找bug极其困难。

如果您希望从StandardError中进行救援,并且需要一个例外的变量,则可以使用此表单:

1
2
3
4
5
begin
  # iceberg!
rescue => e
  # lifeboats
end

相当于:

1
2
3
4
5
begin
  # iceberg!
rescue StandardError => e
  # lifeboats
end

Exception中解救出来是明智的少数常见情况之一,是为了记录/报告的目的,在这种情况下,您应该立即重新提出例外:

1
2
3
4
5
6
begin
  # iceberg?
rescue Exception => e
  # do some logging
  raise e  # not enough lifeboats ;)
end


真正的规则是:不要丢弃异常。你方报价的作者的客观性是有问题的,事实证明它以

or I will stab you

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

1
2
3
4
5
6
7
8
9
10
#! /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,就会发生这种情况)。
  • 那么这在您的连接处理线程中是完全可以接受的:

    开始做某事救援异常=>Emylogger.error("处理连接时未捕获e异常:e.message")mylogger.error("堆栈跟踪:backtrace.map l"l


    假设你在一辆车里(开着红宝石)。您最近安装了一个新的方向盘和空中升级系统(使用eval),但您不知道其中一个程序员在语法上搞砸了。

    你在一座桥上,意识到自己有点朝栏杆走去,于是左转。

    1
    2
    3
    def turn_left
      self.turn left:
    end

    哎呀!那可能不太好?幸运的是,Ruby培养了一个SyntaxError

    车应该马上停下来-对吗?

    不。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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

    Warning: Caught SyntaxError Exception.

    Info: Logged Error - Continuing Process.

    你发现有问题,就猛击紧急刹车(^C号:Interrupt号)

    beep beep

    Warning: Caught Interrupt Exception.

    Info: Logged Error - Continuing Process.

    是的-没什么帮助。你离铁路很近,所以你把车停在了停车场(killing:SignalException)。

    beep beep

    Warning: Caught SignalException Exception.

    Info: Logged Error - Continuing Process.

    在最后一秒钟,你拔出钥匙(kill -9),汽车停了下来,你猛地向前撞向方向盘(因为你没有优雅地停止程序,安全气囊不能充气,你终止了程序),汽车后面的电脑猛地撞到前面的座位上。半罐可乐洒在纸上。后面的杂货被压碎了,大部分都被蛋黄和牛奶包裹着。这辆车需要大修和清洗。(数据丢失)

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

    但是等等!您可能想使用rescue Exception => e的原因有很多。

    假设你就是那辆车,你想确保如果汽车超过了安全的停止动量,安全气囊就会充气。

    1
    2
    3
    4
    5
    6
     begin
        # do driving stuff
     rescue Exception => e
        self.airbags.inflate if self.exceeding_safe_stopping_momentum?
        raise
     end

    规则有一个例外:只有你提出例外,你才能抓住Exception。因此,更好的规则是永远不要吞下Exception,并且总是重新提出错误。

    但是,在Ruby这样的语言中,添加rescue很容易被忘记,并且在重新提出问题之前添加一个rescue语句感觉有点不干燥。你不想忘记江户十一〔十〕号声明。如果你这样做了,祝你好运,找到那个错误。

    幸运的是,Ruby非常棒,您可以只使用ensure关键字,这可以确保代码运行。无论什么情况,ensure关键字都将运行代码-如果抛出异常,如果没有,唯一的异常是世界是否结束(或其他不太可能发生的事件)。

    1
    2
    3
    4
    5
     begin
        # do driving stuff
     ensure
        self.airbags.inflate if self.exceeding_safe_stopping_momentum?
     end

    繁荣!不管怎样,这个代码应该可以运行。您应该使用rescue Exception => e的唯一原因是您需要访问异常,或者您只希望代码在异常上运行。记住要重新提出错误。每一次。

    注意:正如@niall指出的,确保始终运行。这是很好的,因为有时您的程序可能会对您撒谎,而不会抛出异常,即使在出现问题时也是如此。对于重要的任务,比如给安全气囊充气,你需要确保不管发生什么事情都会发生。因此,每次停车时检查是否有异常是个好主意。尽管在大多数编程环境中,给气囊充气是一项不常见的任务,但这实际上在大多数清理任务中都很常见。

    DR

    不要(也不要重新提出例外情况),否则你可能会开下一座桥。


    因为这捕获了所有异常。你的程序不太可能从它们中恢复。

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

    吞咽异常是很糟糕的,不要这样做。


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