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

不要拯救异常。永远。否则我就捅死你

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


当前回答

这是规则的一种特殊情况,您不应该捕获任何您不知道如何处理的异常。如果您不知道如何处理它,最好让系统的其他部分捕捉和处理它。

其他回答

这篇博客文章完美地解释了这一点: Ruby的Exception和StandardError:有什么不同?

Why you shouldn't rescue Exception The problem with rescuing Exception is that it actually rescues every exception that inherits from Exception. Which is....all of them! That's a problem because there are some exceptions that are used internally by Ruby. They don't have anything to do with your app, and swallowing them will cause bad things to happen. Here are a few of the big ones: SignalException::Interrupt - If you rescue this, you can't exit your app by hitting control-c. ScriptError::SyntaxError - Swallowing syntax errors means that things like puts("Forgot something) will fail silently. NoMemoryError - Wanna know what happens when your program keeps running after it uses up all the RAM? Me neither. begin do_something() rescue Exception => e # Don't do this. This will swallow every single exception. Nothing gets past it. end I'm guessing that you don't really want to swallow any of these system-level exceptions. You only want to catch all of your application level errors. The exceptions caused YOUR code. Luckily, there's an easy way to to this. Rescue StandardError Instead All of the exceptions that you should care about inherit from StandardError. These are our old friends: NoMethodError - raised when you try to invoke a method that doesn't exist TypeError - caused by things like 1 + "" RuntimeError - who could forget good old RuntimeError? To rescue errors like these, you'll want to rescue StandardError. You COULD do it by writing something like this: begin do_something() rescue StandardError => e # Only your app's exceptions are swallowed. Things like SyntaxErrror are left alone. end But Ruby has made it much easier for use. When you don't specify an exception class at all, ruby assumes you mean StandardError. So the code below is identical to the above code: begin do_something() rescue => e # This is the same as rescuing StandardError end

博士TL;

不要抢救Exception => e(也不要重新引发异常)——否则你可能会从桥上掉下去。


假设您在一辆车里(运行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

哔哔哔哔的声音 警告:捕获SyntaxError异常。 信息:日志错误-继续进程。

你注意到有些事情不对劲,你猛按紧急开关(^C:中断)

哔哔哔哔的声音 警告:捕获中断异常。 信息:日志错误-继续进程。

是啊,那也没什么用。您非常接近轨道,所以您将汽车停在停车场(杀死: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这样的语言中添加rescue很容易被忘记,而且在重新提出问题之前添加rescue语句感觉有点不dry。不要忘记raise语句。如果你这样做了,祝你能找到那个错误。

值得庆幸的是,Ruby非常棒,您可以只使用ensure关键字,它可以确保代码运行。无论发生什么,ensure关键字都将运行代码——如果抛出异常,如果没有抛出异常,唯一的异常是世界末日(或其他不太可能的事件)。

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

繁荣!无论如何,代码都应该运行。使用rescue Exception => e的唯一原因是需要访问异常,或者只想让代码在异常上运行。记住要重新抛出错误。每一次。

Note: As @Niall pointed out, ensure always runs. This is good because sometimes your program can lie to you and not throw exceptions, even when issues occur. With critical tasks, like inflating airbags, you need to make sure it happens no matter what. Because of this, checking every time the car stops, whether an exception is thrown or not, is a good idea. Even though inflating airbags is a bit of an uncommon task in most programming contexts, this is actually pretty common with most cleanup tasks.

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

否则我就捅死你

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

#! /usr/bin/ruby

while true do
  begin
    line = STDIN.gets
    # heavy processing
  rescue Exception => e
    puts "caught exception #{e}! ohnoes!"
  end
end

不,真的,别这么做。甚至不用运行它来验证它是否有效。

然而,假设你有一个线程服务器,你不希望所有的异常:

被忽略(默认值) 停止服务器(如果您说线程,就会发生这种情况。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

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

注意:

If you've run someone else's program that catches signal exceptions and ignores them, (say the code above) then: in Linux, in a shell, type pgrep ruby, or ps | grep ruby, look for your offending program's PID, and then run kill -9 <PID>. in Windows, use the Task Manager (CTRL-SHIFT-ESC), go to the "processes" tab, find your process, right click it and select "End process". If you are working with someone else's program which is, for whatever reason, peppered with these ignore-exception blocks, then putting this at the top of the mainline is one possible cop-out: %W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" } This causes the program to respond to the normal termination signals by immediately terminating, bypassing exception handlers, with no cleanup. So it could cause data loss or similar. Be careful! If you need to do this: begin do_something rescue Exception => e critical_cleanup raise end you can actually do this: begin do_something ensure critical_cleanup end In the second case, critical cleanup will be called every time, whether or not an exception is thrown.

这是规则的一种特殊情况,您不应该捕获任何您不知道如何处理的异常。如果您不知道如何处理它,最好让系统的其他部分捕捉和处理它。

TL;DR:使用StandardError代替一般异常捕获。当原始异常被重新引发时(例如,当抢救仅记录异常时),抢救exception可能是可以的。


Exception是Ruby异常层次结构的根,所以当你拯救Exception时,你就从所有的类中拯救了,包括SyntaxError、LoadError和Interrupt等子类。

挽救中断阻止用户使用CTRLC退出程序。

拯救SignalException阻止程序正确响应信号。除非使用-9杀伤,否则它将无法被杀死。

拯救SyntaxError意味着失败的求值将以静默的方式执行。

所有这些都可以通过运行这个程序来显示,并尝试CTRLC或杀死它:

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中营救,而是从standardror中营救。您通常应该指定比默认的StandardError更具体的东西,但是从Exception中挽救会扩大而不是缩小范围,并且可能会产生灾难性的结果,并使查找错误变得极其困难。


如果你确实想从standardror中进行营救,并且你需要一个带有异常的变量,你可以使用这个形式:

begin
  # iceberg!
rescue => e
  # lifeboats
end

这相当于:

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

从Exception中抢救是明智的少数常见情况之一是用于记录/报告目的,在这种情况下,你应该立即重新引发异常:

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