Ryan Davis 的 Ruby QuickRef 说(没有解释):
不要拯救异常。曾经。否则我会刺伤你。
为什么不?什么是正确的做法?
TL;DR:使用 StandardError
代替一般异常捕获。当重新引发原始异常时(例如,当救援仅记录异常时),救援 Exception
可能是可以的。
Exception
是 Ruby's exception hierarchy 的根,因此当您 rescue Exception
时,您会从一切中解救出来,包括 SyntaxError
、LoadError
和 Interrupt
等子类。
救援 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
中拯救会扩大而不是缩小范围,并且可能会产生灾难性的结果并使寻找错误变得非常困难。
如果您确实想从 StandardError
中进行救援,并且需要一个带有异常的变量,则可以使用以下形式:
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
真正的规则是:不要丢弃异常。您引用的作者的客观性值得怀疑,这可以从它以
否则我会刺伤你
当然,请注意信号(默认情况下)会抛出异常,并且通常长时间运行的进程会通过信号终止,因此捕获异常而不终止信号异常将使您的程序很难停止。所以不要这样做:
#! /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 ruby 或 ps | grep ruby,查找有问题的程序的 PID,然后运行 kill -9
ensure
都会运行,而 rescue
只有在引发异常时才会运行。
The objectivity of the author of your quote is questionable
”。这家伙写了 minitest 和大量其他广泛使用的 gem。 blog.zenspider.com/projects
TL;博士
不要rescue 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
beep beep 警告:捕获 SyntaxError 异常。信息:记录的错误 - 继续过程。
您发现有问题,然后猛踩紧急休息时间 (^C
: Interrupt
)
哔哔警告:捕获中断异常。信息:记录的错误 - 继续过程。
是的 - 这没有多大帮助。您离铁路很近,因此您将汽车停在了停车位(kill
ing:SignalException
)。
beep beep 警告:捕获 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 这样的语言中添加救援很容易忘记,并且在重新提出问题之前放置救援语句感觉有点不干。而且您不想忘记 raise
语句。如果你这样做了,祝你好运,试图找到那个错误。
值得庆幸的是,Ruby 非常棒,您只需使用 ensure
关键字即可确保代码运行。无论如何,ensure
关键字都会运行代码 - 如果抛出异常,如果没有,唯一的异常是世界结束(或其他不太可能发生的事件)。
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
繁荣!并且该代码无论如何都应该运行。您应该使用 rescue Exception => e
的唯一原因是您需要访问异常,或者您只希望代码在异常上运行。并记得重新提出错误。每次。
注意:正如@Niall 指出的,确保始终运行。这很好,因为有时您的程序可能会欺骗您并且不会抛出异常,即使发生问题也是如此。对于关键任务,例如给安全气囊充气,您需要确保它无论如何都会发生。正因为如此,每次停车时检查一下是否抛出异常是一个好主意。尽管在大多数编程环境中给安全气囊充气是一项不常见的任务,但这实际上在大多数清理任务中很常见。
ensure
替代 rescue Exception
的部分具有误导性 - 该示例暗示它们是等效的,但正如所述 ensure
将发生无论是否存在异常,所以现在你的安全气囊会膨胀,因为你过去了5mph,即使没有出错。
self.exceeding_safe_stopping_momentum?
更改超过 5 英里/小时的误导性检查。我还添加了一个解释器,说明为什么在这种情况下要使用 ensure。 rescue Exception
并不总是错误的,但 ensure
是进行清理的最佳时机,因为即使您的程序静默失败,也会发生这种情况。
因为这会捕获所有异常。您的程序不太可能从其中任何一个中恢复。
您应该只处理您知道如何从中恢复的异常。如果您没有预料到某种异常,请不要处理它,大声崩溃(将详细信息写入日志),然后诊断日志并修复代码。
吞咽异常是不好的,不要这样做。
这是规则的一个特定情况,您不应该捕获任何您不知道如何处理的异常。如果您不知道如何处理它,最好让系统的其他部分捕获并处理它。
这篇博文完美地解释了这一点:Ruby's Exception vs StandardError: What's the difference?
为什么你不应该抢救 Exception 抢救 Exception 的问题在于它实际上抢救了每个继承自 Exception 的异常。这是....所有这些!这是一个问题,因为 Ruby 内部使用了一些异常。它们与您的应用程序没有任何关系,吞下它们会导致坏事发生。以下是其中一些重要的: SignalException::Interrupt - 如果你救了这个,你不能通过点击 control-c 来退出你的应用程序。 ScriptError::SyntaxError - 吞下语法错误意味着 puts("Forgot something) 之类的东西会默默地失败。NoMemoryError - 想知道当你的程序在用完所有 RAM 后继续运行时会发生什么?我也没有。开始 do_something() 救援异常=> e # 不要这样做。这会吞下每一个异常。没有什么能通过它。结束我猜你真的不想吞下任何这些系统级异常。你只想捕获所有您的应用程序级错误。异常导致了您的代码。幸运的是,有一个简单的方法来解决这个问题。拯救 StandardError 而不是您应该关心的所有异常都继承自 StandardError。这些是我们的老朋友:NoMethodError - 在您尝试时引发调用一个不存在的方法 TypeError - 由 1 + "" RuntimeError 之类的东西引起 - 谁会忘记旧的 RuntimeError?要挽救这样的错误,您需要挽救 StandardError。您可以通过编写类似的东西来做到这一点这个: begin do_something() rescue StandardError => e # 只有你的应用程序的异常被吞没。像 SyntaxError 这样的东西就不用管了。 end 但是 Ruby 使它更易于使用。当您根本没有指定异常类时,ruby 假定您的意思是 StandardError。所以下面的代码和上面的代码是一样的: begin do_something() rescue => e # 这和rescuing StandardError一样 end
Throwable
ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]
,然后再rescue *ADAPTER_ERRORS => e