TL; DR :使用StandardError
代替一般异常捕获。当重新引发原始异常时(例如,仅在执行救援以记录异常时),抢救Exception
可能没问题。
Exception
是Ruby 的异常层次结构的根源,因此当您rescue Exception
时,您可以从所有内容中 LoadError
,包括子类,如SyntaxError
, LoadError
和Interrupt
。
Rescuing Interrupt
可防止用户使用CTRL C退出程序。
抢救SignalException
阻止程序正确响应信号。除了kill -9
之外它将是不可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
解救并且需要具有异常的变量,则可以使用以下形式:
begin
# iceberg!
rescue => e
# lifeboats
end
这相当于:
begin
# iceberg!
rescue StandardError => e
# lifeboats
end
从Exception
拯救的几个常见案例之一是用于记录 / 报告目的,在这种情况下,您应该立即重新引发异常:
begin
# iceberg?
rescue Exception => e
# do some logging
raise e # 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!没有人这样做!)但你可能会因为某种原因暂时保留它一段时间,但不能继续运行你的调试器。
注意:
如果你运行别人的程序捕获信号异常并忽略它们(比如上面的代码)那么:
pgrep ruby
或ps | grep ruby
,查找违规程序的 PID,然后运行kill -9 <PID>
。 如果您正在使用其他人的程序,无论出于何种原因,使用这些忽略异常块,那么将其放在主线的顶部就是一个可能的问题:
%W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
这导致程序通过立即终止,绕过异常处理程序而不进行清理来响应正常终止信号。因此它可能导致数据丢失或类似。小心!
如果你需要这样做:
begin
do_something
rescue Exception => e
critical_cleanup
raise
end
你实际上可以这样做:
begin
do_something
ensure
critical_cleanup
end
在第二种情况下,每次都会调用critical cleanup
,无论是否抛出异常。
假设你在车里(运行 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
: Interrupt
)
哔哔
警告:捕获中断异常。
信息:记录错误 - 继续过程。
是的 - 这没多大帮助。你非常靠近铁轨,所以你把车停在公园里( kill
: SignalException
)。
哔哔
警告:Caught 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 指出的那样,确保始终运行。这很好,因为有时你的程序会骗你,而不会抛出异常,即使出现问题。对于像气囊充气这样的关键任务,无论如何都要确保它发生。因此,每次停车检查是否抛出异常都是一个好主意。尽管在大多数编程环境中充气安全气囊是一项不常见的任务,但实际上这对于大多数清理任务来说都很常见。
不要rescue Exception => e
(而不是重新引发异常) - 或者你可能会驱逐一座桥。