Ryan Davis的Ruby QuickRef说(没有解释):
不要救援Exception . EVER . 或者我会刺伤你
为什么不?什么是正确的做法?
TL;DR :使用 StandardError 代替一般异常捕获 . 当重新引发原始异常时(例如,仅在执行救援以记录异常时),抢救 Exception 可能没问题 .
StandardError
Exception
Exception 是Ruby's exception hierarchy的根,所以当你__63839_时,你可以从所有东西中解救出来,包括诸如 SyntaxError , LoadError 和 Interrupt 之类的子类 .
SyntaxError
LoadError
Interrupt
抢救 Interrupt 会阻止用户使用CTRLC退出程序 .
拯救 SignalException 可防止程序正确响应信号 . 除了 kill -9 之外,它将是不可杀戮的 .
SignalException
kill -9
拯救 SyntaxError 意味着失败的 eval 将默默地这样做 .
eval
所有这些都可以通过运行此程序,并尝试CTRLC或 kill 来显示:
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
那个's a specific case of the rule that you shouldn't会 grab 你不知道如何处理它的任何异常,让系统的其他部分 grab 并处理它总是更好 .
真正的规则是:不要抛弃异常 . 你引用的作者的客观性是值得怀疑的,正如它的结尾所证明的那样
或者我会刺伤你
当然,请注意信号(默认情况下)会抛出异常,通常长时间运行的进程会通过信号终止,因此捕获异常而不是终止信号异常将使您的程序很难停止 . 所以不要这样做:
#! /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 就会发生这种情况) .
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" .
do_something
a
"something else"
一般情况下,除非在您不需要担心的特殊情况下,否则不要这样做 . 一个例子:
debugger rescue nil
debugger 函数是在代码中设置断点的一种相当不错的方法,但如果在调试器和Rails之外运行,则会引发异常 . 从理论上讲,你不应该在你的程序中留下调试代码(pff!没有人这样做!)但是你可能因为某些原因想要将它保留一段时间,但不能继续运行你的调试器 .
debugger
注意:
如果你运行别人的程序捕获信号异常并忽略它们(比如上面的代码)那么:
在Linux中,在shell中,键入 pgrep ruby 或 ps | grep ruby ,查找违规程序的PID,然后运行 kill -9 <PID> .在Windows中
pgrep ruby
ps | grep ruby
kill -9 <PID>
,使用任务管理器(CTRL-SHIFT-ESC),转到"processes"选项卡,找到您的进程,右键单击它并选择"End process" .
如果您正在使用其他人的程序,无论出于何种原因,这些程序都会使用这些忽略异常块,那么将其放在主线的顶部就是一个可能的问题:
%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 ,无论是否抛出异常 .
critical cleanup
因为这会捕获所有异常 . 你的程序不太可能从 any 中恢复 .
您应该只处理您知道如何从中恢复的异常 . 如果您没有预料到某种异常,请不要处理它,大声崩溃(将详细信息写入日志),然后诊断日志并修复代码 .
吞咽异常是不好的,不要这样做 .
假设你在车里(运行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警告:Caught SyntaxError异常 . 信息:记录错误 - 继续过程 .
你注意到有些事情是错的,你在紧急休息时砰的一声( ^C : Interrupt )
^C
蜂鸣声警告:捕获中断异常 . 信息:记录错误 - 继续过程 .
是的 - 那没有't help much. You'非常接近铁路,所以你把车停在公园( kill ing: SignalException ) .
蜂鸣声警告:捕获SignalException异常 . 信息:记录错误 - 继续过程 .
在最后一秒,你拉出钥匙( kill -9 ),然后汽车停下来,你向前撞到方向盘(安全气囊可以优雅地停止程序 - 你终止了它),你的后面的电脑汽车撞到了前面的座位上 . 半满的可乐可以溢出纸张 . 背面的杂货被粉碎,大部分都是蛋黄和牛奶 . 汽车需要严重的维修和清洁 . (数据丢失)
希望你有保险(备份) . 哦是的 - 因为安全气囊没有膨胀,你可能会受伤(被解雇等) .
可是等等!您可能想要使用 rescue Exception => e 的原因更多!
rescue Exception => e
让我们说你是那辆车,如果汽车超过其安全停止动力,你想确保安全气囊膨胀 .
begin # do driving stuff rescue Exception => e self.airbags.inflate if self.exceeding_safe_stopping_momentum? raise end
这是规则的例外:你可以 grab Exception only if you re-raise the exception . 所以,更好的规则是永远不要吞下 Exception ,并且总是重新引发错误 .
但是,添加救援在Ruby这样的语言中很容易被遗忘,并且在重新提出问题之前立即发布救援声明感觉有点不干 . 而你 do not 想要忘记 raise 声明 . 如果你这样做,祝你好运,试图找到那个错误 .
raise
值得庆幸的是,Ruby非常棒,你可以使用 ensure 关键字来确保代码运行 . ensure 关键字将运行代码,无论如何 - 如果抛出异常,如果不抛出异常,唯一的例外是世界结束(或其他不太可能发生的事件) .
ensure
begin # do driving stuff ensure self.airbags.inflate if self.exceeding_safe_stopping_momentum? end
繁荣!并且该代码应该运行 . 您应该使用 rescue Exception => e 的唯一原因是您需要访问异常,或者您只希望代码在异常上运行 . 并记得重新提出错误 . 每次 .
注意:正如@Niall指出的那样,确保运行 always . 这很好,因为有时你的程序可能骗你,而不会抛出异常,即使出现问题 . 对于像气囊充气这样的关键任务,无论如何都要确保它发生 . 因此,每次停车检查是否抛出异常都是一个好主意 . 尽管在大多数编程环境中充气安全气囊是一项不常见的任务,但实际上这在大多数清理任务中都很常见 .
不要 rescue Exception => e (而不是重新提高异常) - 或者你可能会开出一座桥 .
5 回答
TL;DR :使用
StandardError
代替一般异常捕获 . 当重新引发原始异常时(例如,仅在执行救援以记录异常时),抢救Exception
可能没问题 .Exception
是Ruby's exception hierarchy的根,所以当你__63839_时,你可以从所有东西中解救出来,包括诸如SyntaxError
,LoadError
和Interrupt
之类的子类 .抢救
Interrupt
会阻止用户使用CTRLC退出程序 .拯救
SignalException
可防止程序正确响应信号 . 除了kill -9
之外,它将是不可杀戮的 .拯救
SyntaxError
意味着失败的eval
将默默地这样做 .所有这些都可以通过运行此程序,并尝试CTRLC或
kill
来显示:从
Exception
抢救甚至不是默认值 . 干不从
Exception
营救,它从StandardError
救出 . 你通常应该指定比默认值StandardError
更具体的东西,但是从Exception
开始拯救范围而不是缩小范围,并且可能产生灾难性的结果并使得捕获bug非常困难 .如果您确实希望从
StandardError
进行救援,并且您需要具有异常的变量,则可以使用以下表单:这相当于:
从
Exception
开始拯救的几个常见案例之一是用于记录/报告目的,在这种情况下,您应该立即重新引发异常:那个's a specific case of the rule that you shouldn't会 grab 你不知道如何处理它的任何异常,让系统的其他部分 grab 并处理它总是更好 .
真正的规则是:不要抛弃异常 . 你引用的作者的客观性是值得怀疑的,正如它的结尾所证明的那样
当然,请注意信号(默认情况下)会抛出异常,通常长时间运行的进程会通过信号终止,因此捕获异常而不是终止信号异常将使您的程序很难停止 . 所以不要这样做:
不,真的,不要这样做 . 甚至不要运行它以查看它是否有效 .
但是,假设您有一个线程服务器,并且您希望所有例外都没有:
忽略
(默认值)
停止服务器(如果你说
thread.abort_on_exception = true
就会发生这种情况) .那么这在您的连接处理线程中是完全可以接受的:
以上内容适用于Ruby的默认异常处理程序的变体,其优点是它不会杀死您的程序 . Rails在其请求处理程序中执行此操作 .
主线程中引发了信号异常 . 后台线程无法获取它们,因此尝试在那里捕获它们是没有意义的 .
这在 生产环境 环境中特别有用,在这种环境中,您不希望程序在出现问题时立即停止 . 然后,您可以在日志中获取堆栈转储并添加到您的代码中,以便在调用链的更下方以更优雅的方式处理特定异常 .
另请注意,还有另一个Ruby习语具有相同的效果:
在这一行中,如果
do_something
引发异常,它将被Ruby捕获,被丢弃,并且a
被分配"something else"
.一般情况下,除非在您不需要担心的特殊情况下,否则不要这样做 . 一个例子:
debugger
函数是在代码中设置断点的一种相当不错的方法,但如果在调试器和Rails之外运行,则会引发异常 . 从理论上讲,你不应该在你的程序中留下调试代码(pff!没有人这样做!)但是你可能因为某些原因想要将它保留一段时间,但不能继续运行你的调试器 .注意:
如果你运行别人的程序捕获信号异常并忽略它们(比如上面的代码)那么:
在Linux中,在shell中,键入
pgrep ruby
或ps | grep ruby
,查找违规程序的PID,然后运行kill -9 <PID>
.在Windows中
,使用任务管理器(CTRL-SHIFT-ESC),转到"processes"选项卡,找到您的进程,右键单击它并选择"End process" .
如果您正在使用其他人的程序,无论出于何种原因,这些程序都会使用这些忽略异常块,那么将其放在主线的顶部就是一个可能的问题:
这导致程序通过立即终止,绕过异常处理程序而不进行清理来响应正常终止信号 . 因此可能导致数据丢失或类似情况 . 小心!
你实际上可以这样做:
在第二种情况下,每次都会调用
critical cleanup
,无论是否抛出异常 .因为这会捕获所有异常 . 你的程序不太可能从 any 中恢复 .
您应该只处理您知道如何从中恢复的异常 . 如果您没有预料到某种异常,请不要处理它,大声崩溃(将详细信息写入日志),然后诊断日志并修复代码 .
吞咽异常是不好的,不要这样做 .
假设你在车里(运行Ruby) . 您最近安装了一个带有无线升级系统(使用
eval
)的新方向盘,但您不知道其中一个程序员搞砸了语法 .你在桥上,意识到你正朝着栏杆前进,所以你向左转 .
哎呀!这可能不是很好,幸运的是,Ruby提出了一个
SyntaxError
.汽车应立即停止 - 对吧?
不 .
你注意到有些事情是错的,你在紧急休息时砰的一声(
^C
:Interrupt
)是的 - 那没有't help much. You'非常接近铁路,所以你把车停在公园(
kill
ing:SignalException
) .在最后一秒,你拉出钥匙(
kill -9
),然后汽车停下来,你向前撞到方向盘(安全气囊可以优雅地停止程序 - 你终止了它),你的后面的电脑汽车撞到了前面的座位上 . 半满的可乐可以溢出纸张 . 背面的杂货被粉碎,大部分都是蛋黄和牛奶 . 汽车需要严重的维修和清洁 . (数据丢失)希望你有保险(备份) . 哦是的 - 因为安全气囊没有膨胀,你可能会受伤(被解雇等) .
可是等等!您可能想要使用
rescue Exception => e
的原因更多!让我们说你是那辆车,如果汽车超过其安全停止动力,你想确保安全气囊膨胀 .
这是规则的例外:你可以 grab
Exception
only if you re-raise the exception . 所以,更好的规则是永远不要吞下Exception
,并且总是重新引发错误 .但是,添加救援在Ruby这样的语言中很容易被遗忘,并且在重新提出问题之前立即发布救援声明感觉有点不干 . 而你 do not 想要忘记
raise
声明 . 如果你这样做,祝你好运,试图找到那个错误 .值得庆幸的是,Ruby非常棒,你可以使用
ensure
关键字来确保代码运行 .ensure
关键字将运行代码,无论如何 - 如果抛出异常,如果不抛出异常,唯一的例外是世界结束(或其他不太可能发生的事件) .繁荣!并且该代码应该运行 . 您应该使用
rescue Exception => e
的唯一原因是您需要访问异常,或者您只希望代码在异常上运行 . 并记得重新提出错误 . 每次 .注意:正如@Niall指出的那样,确保运行 always . 这很好,因为有时你的程序可能骗你,而不会抛出异常,即使出现问题 . 对于像气囊充气这样的关键任务,无论如何都要确保它发生 . 因此,每次停车检查是否抛出异常都是一个好主意 . 尽管在大多数编程环境中充气安全气囊是一项不常见的任务,但实际上这在大多数清理任务中都很常见 .
TL; DR
不要
rescue Exception => e
(而不是重新提高异常) - 或者你可能会开出一座桥 .