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

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

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


当前回答

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

其他回答

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

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

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

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

这篇博客文章完美地解释了这一点: 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

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

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

否则我就捅死你

当然,要注意信号(默认情况下)会抛出异常,通常长时间运行的进程会通过信号终止,因此捕捉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.