Ryan Davis的Ruby QuickRef说(没有解释):
不要拯救异常。永远。否则我就捅死你
为什么不呢?正确的做法是什么?
Ryan Davis的Ruby QuickRef说(没有解释):
不要拯救异常。永远。否则我就捅死你
为什么不呢?正确的做法是什么?
当前回答
这篇博客文章完美地解释了这一点: 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
其他回答
这篇博客文章完美地解释了这一点: 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
这是规则的一种特殊情况,您不应该捕获任何您不知道如何处理的异常。如果您不知道如何处理它,最好让系统的其他部分捕捉和处理它。
博士TL;
不要抢救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
哔哔哔哔的声音 警告:捕获SyntaxError异常。 信息:日志错误-继续进程。
你注意到有些事情不对劲,你猛按紧急开关(^C:中断)
哔哔哔哔的声音 警告:捕获中断异常。 信息:日志错误-继续进程。
是啊,那也没什么用。您非常接近轨道,所以您将汽车停在停车场(杀死: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这样的语言中添加rescue很容易被忘记,而且在重新提出问题之前添加rescue语句感觉有点不dry。不要忘记raise语句。如果你这样做了,祝你能找到那个错误。
值得庆幸的是,Ruby非常棒,您可以只使用ensure关键字,它可以确保代码运行。无论发生什么,ensure关键字都将运行代码——如果抛出异常,如果没有抛出异常,唯一的异常是世界末日(或其他不太可能的事件)。
begin
# do driving stuff
ensure
self.airbags.inflate if self.exceeding_safe_stopping_momentum?
end
繁荣!无论如何,代码都应该运行。使用rescue Exception => e的唯一原因是需要访问异常,或者只想让代码在异常上运行。记住要重新抛出错误。每一次。
Note: As @Niall pointed out, ensure always runs. 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:使用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
因为这捕获了所有异常。您的程序不太可能从其中任何一个恢复。
您应该只处理您知道如何从中恢复的异常。如果您没有预料到某种类型的异常,就不要处理它,大声崩溃(将详细信息写入日志),然后诊断日志并修复代码。
吞下异常是不好的,不要这样做。