本文翻译自:Why is it bad style to `rescue Exception => e` in Ruby?
Ryan Davis's Ruby QuickRef says (without explanation): Ryan Davis的Ruby QuickRef说(没有解释):
Don't rescue Exception. 不要挽救异常。 EVER. 永远 or I will stab you. 不然我会刺你
Why not? 为什么不? What's the right thing to do? 正确的做法是什么?
#1楼
参考:https://stackoom.com/question/g9zJ/为什么在Ruby中使用-rescue-Exception-gt-e-样式不好
#2楼
Because this captures all exceptions. 因为这捕获了所有异常。 It's unlikely that your program can recover from any of them. 您的程序不太可能从其中任何一个恢复。
You should handle only exceptions that you know how to recover from. 您应该只处理知道如何恢复的异常。 If you don't anticipate a certain kind of exception, don't handle it, crash loudly (write details to the log), then diagnose logs and fix code. 如果您不希望发生某种异常,请不要处理它,大声崩溃(将详细信息写入日志),然后诊断日志并修复代码。
Swallowing exceptions is bad, don't do this. 吞咽异常是不好的,不要这样做。
#3楼
TL;DR : Use StandardError
instead for general exception catching. TL; DR :使用StandardError
代替常规异常捕获。 When the original exception is re-raised (eg when rescuing to log the exception only), rescuing Exception
is probably okay. 当原始异常被重新引发时(例如,当挽救仅记录该异常时),挽救Exception
可能是可以的。
Exception
is the root of Ruby's exception hierarchy , so when you rescue Exception
you rescue from everything , including subclasses such as SyntaxError
, LoadError
, and Interrupt
. Exception
是Ruby异常层次结构的根,因此,当您rescue Exception
时, LoadError
从所有内容中抢救LoadError
,包括SyntaxError
, LoadError
和Interrupt
等子类。
Rescuing Interrupt
prevents the user from using CTRL C to exit the program. Rescuing Interrupt
阻止用户使用CTRL C退出程序。
Rescuing SignalException
prevents the program from responding correctly to signals. 抢救SignalException
阻止程序正确响应信号。 It will be unkillable except by kill -9
. 除了被kill -9
以外,它是无法kill -9
。
Rescuing SyntaxError
means that eval
s that fail will do so silently. 拯救SyntaxError
意味着失败的eval
会静默地这样做。
All of these can be shown by running this program, and trying to CTRL C or kill
it: 所有这些都可以通过运行该程序并尝试按CTRL C或kill
它来显示:
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception
puts "I refuse to fail or be stopped!"
end
end
Rescuing from Exception
isn't even the default. 从Exception
救援甚至都不是默认的。 Doing 在做
begin
# iceberg!
rescue
# lifeboats
end
does not rescue from Exception
, it rescues from StandardError
. 不能从Exception
救援,而是从StandardError
救援。 You should generally specify something more specific than the default StandardError
, but rescuing from Exception
broadens the scope rather than narrowing it, and can have catastrophic results and make bug-hunting extremely difficult. 通常,您应该指定比默认的StandardError
更具体的内容,但是从Exception
救援可以扩大范围,而不是缩小范围,并且可能带来灾难性的结果,并且使Bug搜寻极其困难。
If you have a situation where you do want to rescue from StandardError
and you need a variable with the exception, you can use this form: 如果您确实想从StandardError
恢复,并且需要一个例外的变量,则可以使用以下形式:
begin
# iceberg!
rescue => e
# lifeboats
end
which is equivalent to: 等效于:
begin
# iceberg!
rescue StandardError => e
# lifeboats
end
One of the few common cases where it's sane to rescue from Exception
is for logging/reporting purposes, in which case you should immediately re-raise the exception: 从Exception
抢救的几种常见情况之一是出于记录/报告的目的,在这种情况下,您应该立即重新引发异常:
begin
# iceberg?
rescue Exception => e
# do some logging
raise # not enough lifeboats ;)
end
#4楼
That's a specific case of the rule that you shouldn't catch any exception you don't know how to handle. 这是规则的一个特例,您不应捕获任何您不知道如何处理的异常。 If you don't know how to handle it, it's always better to let some other part of the system catch and handle it. 如果您不知道如何处理它,最好让系统的其他部分捕获并处理它。
#5楼
The real rule is: Don't throw away exceptions. 真正的规则是:不要丢弃异常。 The objectivity of the author of your quote is questionable, as evidenced by the fact that it ends with 您的报价作者的客观性值得怀疑,事实证明是这样的事实:
or I will stab you 不然我会刺你
Of course, be aware that signals (by default) throw exceptions, and normally long-running processes are terminated through a signal, so catching Exception and not terminating on signal exceptions will make your program very hard to stop. 当然,请注意信号(默认情况下)会引发异常,并且通常长时间运行的进程会通过信号终止,因此捕获Exception而不终止信号异常将使您的程序很难停止。 So don't do this: 所以不要这样做:
#! /usr/bin/ruby
while true do
begin
line = STDIN.gets
# heavy processing
rescue Exception => e
puts "caught exception #{e}! ohnoes!"
end
end
No, really, don't do it. 不,真的,不要这样做。 Don't even run that to see if it works. 甚至不要运行它来查看它是否有效。
However, say you have a threaded server and you want all exceptions to not: 但是,假设您有一台线程服务器,并且您不希望所有异常:
- be ignored (the default) 被忽略(默认)
- stop the server (which happens if you say
thread.abort_on_exception = true
). 停止服务器(如果您说thread.abort_on_exception = true
发生)。
Then this is perfectly acceptable in your connection handling thread: 然后,这在您的连接处理线程中是完全可以接受的:
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
The above works out to a variation of Ruby's default exception handler, with the advantage that it doesn't also kill your program. 上面是Ruby默认异常处理程序的一种变体,它的优点是不会杀死您的程序。 Rails does this in its request handler. Rails在其请求处理程序中执行此操作。
Signal exceptions are raised in the main thread. 信号异常在主线程中引发。 Background threads won't get them, so there is no point in trying to catch them there. 后台线程不会获取它们,因此尝试将它们捕获在那里是没有意义的。
This is particularly useful in a production environment, where you do not want your program to simply stop whenever something goes wrong. 这在生产环境中特别有用,在生产环境中,您不希望程序在出现问题时仅停止运行。 Then you can take the stack dumps in your logs and add to your code to deal with specific exception further down the call chain and in a more graceful manner. 然后,您可以将堆栈转储记录到日志中,并添加到您的代码中,以更优雅的方式在调用链中进一步处理特定异常。
Note also that there is another Ruby idiom which has much the same effect: 还请注意,还有另一个Ruby惯用语具有相同的效果:
a = do_something rescue "something else"
In this line, if do_something
raises an exception, it is caught by Ruby, thrown away, and a
is assigned "something else"
. 在这一行中,如果do_something
引发异常,它将被Ruby捕获并丢弃,并为a
分配"something else"
。
Generally, don't do that, except in special cases where you know you don't need to worry. 一般情况下,不这样做,除非是在你知道你并不需要担心特殊情况。 One example: 一个例子:
debugger rescue nil
The debugger
function is a rather nice way to set a breakpoint in your code, but if running outside a debugger, and Rails, it raises an exception. debugger
功能是在代码中设置断点的一种不错的方法,但是如果在调试器和Rails之外运行,则会引发异常。 Now theoretically you shouldn't be leaving debug code lying around in your program (pff! nobody does that!) but you might want to keep it there for a while for some reason, but not continually run your debugger. 从理论上讲,现在您不应该将调试代码留在程序中(pff!没有人这样做!),但是出于某种原因,您可能希望将其保留一段时间,但不要连续运行调试器。
Note: 注意:
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
, orps | grep ruby
在Linux的shell中,键入pgrep ruby
或ps | grep ruby
ps | grep ruby
, look for your offending program's PID, and then runkill -9 <PID>
.ps | grep ruby
,查找有问题的程序的PID,然后运行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". 在Windows中,使用任务管理器( CTRL - SHIFT - ESC ),转到“进程”选项卡,找到您的进程,右键单击它,然后选择“结束进程”。
- in Linux, in a shell, type
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: 如果您正在使用别人的程序,无论出于何种原因,这些程序都充斥着这些ignore-exception块,那么将其放在主线顶部是一种可能的解决方案:
%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. 在第二种情况下,无论是否引发异常,都会每次都调用critical cleanup
。
#6楼
Let's say you are in a car (running Ruby). 假设您正在开车(正在运行Ruby)。 You recently installed a new steering wheel with the over-the-air upgrade system (which uses eval
), but you didn't know one of the programmers messed up on syntax. 您最近使用空中升级系统(使用eval
)安装了一个新的方向盘,但您不知道其中一位程序员对语法感到困惑。
You are on a bridge, and realize you are going a bit towards the railing, so you turn left. 您在桥上,意识到自己要驶向栏杆,所以您向左转。
def turn_left
self.turn left:
end
oops! 哎呀! That's probably Not Good ™, luckily, Ruby raises a SyntaxError
. 幸运的是,这可能不是Good ™,Ruby引发了SyntaxError
。
The car should stop immediately - right? 汽车应该立即停止-对吗?
Nope. 不。
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 哔哔
Warning: Caught SyntaxError Exception. 警告:捕获到SyntaxError异常。
Info: Logged Error - Continuing Process. 信息:记录的错误-继续过程。
You notice something is wrong, and you slam on the emergency breaks ( ^C
: Interrupt
) 您发现出了点问题,然后在紧急情况下大满贯( ^C
: Interrupt
)
beep beep 哔哔
Warning: Caught Interrupt Exception. 警告:捕获到中断异常。
Info: Logged Error - Continuing Process. 信息:记录的错误-继续过程。
Yeah - that didn't help much. 是的-没什么帮助。 You're pretty close to the rail, so you put the car in park ( kill
ing: SignalException
). 您非常靠近铁路,因此将汽车停在了停车场( kill
: SignalException
)。
beep beep 哔哔
Warning: Caught SignalException Exception. 警告:捕获到SignalException异常。
Info: Logged Error - Continuing Process. 信息:记录的错误-继续过程。
At the last second, you pull out the keys ( kill -9
), and the car stops, you slam forward into the steering wheel (the airbag can't inflate because you didn't gracefully stop the program - you terminated it), and the computer in the back of your car slams into the seat in front of it. 在最后一秒钟,您拔出钥匙( kill -9
),汽车停下来,向前猛撞到方向盘(安全气囊无法充气,因为您没有适当地停止程序,因此终止了程序),汽车后座的电脑就撞到了它前面的座位上。 A half-full can of Coke spills over the papers. 半满的可乐罐溢出了报纸。 The groceries in the back are crushed, and most are covered in egg yolk and milk. 后面的杂物被压碎,大部分被蛋黄和牛奶覆盖。 The car needs serious repair and cleaning. 该车需要认真维修和清洁。 (Data Loss) (数据丢失)
Hopefully you have insurance (Backups). 希望您有保险(备用)。 Oh yeah - because the airbag didn't inflate, you're probably hurt (getting fired, etc). 哦,是的-由于安全气囊没有膨胀,您可能受伤了(被解雇了,等等)。
But wait! 可是等等! There's 有 more 更多 reasons why you might want to use rescue Exception => e
! 您可能要使用rescue Exception => e
!
Let's say you're that car, and you want to make sure the airbag inflates if the car is exceeding its safe stopping momentum. 假设您是那辆汽车,并且如果汽车超过其安全的停止动量,则要确保安全气囊膨胀。
begin
# do driving stuff
rescue Exception => e
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
raise
end
Here's the exception to the rule: You can catch Exception
only if you re-raise the exception . 这是规则的例外: 仅当您重新引发exception时才能捕获Exception
。 So, a better rule is to never swallow Exception
, and always re-raise the error. 因此,更好的规则是永远不要吞下Exception
,并且总是重新引发错误。
But adding rescue is both easy to forget in a language like Ruby, and putting a rescue statement right before re-raising an issue feels a little non-DRY. 但是在使用Ruby之类的语言时,添加救援既容易,又在重新提出问题之前放下救援声明会感到有些不干。 And you do not want to forget the raise
statement. 而且您也不想忘记raise
声明。 And if you do, good luck trying to find that error. 如果这样做,祝您找到这个错误,祝您好运。
Thankfully, Ruby is awesome, you can just use the ensure
keyword, which makes sure the code runs. 值得庆幸的是,Ruby是真棒,你可以只使用ensure
关键字,它可以确保代码运行。 The ensure
keyword will run the code no matter what - if an exception is thrown, if one isn't, the only exception being if the world ends (or other unlikely events). ensure
关键字无论如何都将运行代码-如果引发异常,如果没有引发异常,则唯一的例外是世界结束(或其他不太可能发生的事件)。
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
Boom! 繁荣! And that code should run anyways. 而且该代码无论如何都应该运行。 The only reason you should use rescue Exception => e
is if you need access to the exception, or if you only want code to run on an exception. 应该使用rescue Exception => e
的唯一原因是,如果您需要访问该异常,或者仅希望代码在该异常上运行。 And remember to re-raise the error. 并记得重新提出该错误。 Every time. 每次。
Note: As @Niall pointed out, ensure always runs. 注意:正如@Niall指出的那样,请确保始终运行。 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. 尽管在大多数编程情况下,安全气囊的充气并不常见,但实际上在大多数清理任务中还是很常见的。
TL;DR TL; DR
Don't rescue Exception => e
(and not re-raise the exception) - or you might drive off a bridge. 不要rescue Exception => e
(不要重新引发该异常)-否则您可能会驶离桥梁。