提问人:John 提问时间:4/7/2012 最后编辑:Andrew MarshallJohn 更新时间:2/19/2023 访问量:346228
为什么在 Ruby 中“拯救 Exception => e”是不好的风格?
Why is it bad style to `rescue Exception => e` in Ruby?
答:
因为这会捕获所有异常。您的程序不太可能从其中任何一个中恢复。
您应该只处理您知道如何从中恢复的异常。如果您没有预料到某种类型的异常,请不要处理它,大声崩溃(将详细信息写入日志),然后诊断日志并修复代码。
吞咽异常是不好的,不要这样做。
TL的;DR:改用于常规异常捕获。当重新引发原始异常时(例如,当拯救仅记录异常时),拯救可能是可以的。StandardError
Exception
Exception
是 Ruby 异常层次结构的根源,所以当你从一切事物中拯救出来时,包括 、 和 .rescue Exception
SyntaxError
LoadError
Interrupt
救援可防止用户使用退出程序。Interrupt
CTRLC
救援会阻止程序正确响应信号。除非 .SignalException
kill -9
拯救意味着失败的人会默默地这样做。SyntaxError
eval
所有这些都可以通过运行这个程序来显示,并尝试或它:CTRLCkill
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
不从中拯救,它从中拯救。您通常应该指定比默认值更具体的内容,但从中救援会扩大范围而不是缩小范围,并且可能会产生灾难性的结果,并使 bug 搜寻变得极其困难。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
评论
Throwable
ADAPTER_ERRORS = [::ActiveRecord::StatementInvalid, PGError, Mysql::Error, Mysql2::Error, ::ActiveRecord::JDBCError, SQLite3::Exception]
rescue *ADAPTER_ERRORS => e
这是规则的一个特定情况,你不应该发现任何你不知道如何处理的异常。如果您不知道如何处理它,最好让系统的其他部分捕获并处理它。
真正的规则是:不要抛弃例外。你引用的作者的客观性是值得怀疑的,它以
否则我会捅你一刀
当然,请注意,信号(默认情况下)会抛出异常,并且通常长时间运行的进程会通过信号终止,因此捕获异常而不在信号异常时终止将使您的程序很难停止。所以不要这样做:
#! /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"
在这一行中,如果引发异常,它将被 Ruby 捕获、丢弃并被分配。do_something
a
"something else"
一般来说,不要这样做,除非在特殊情况下,你知道你不需要担心。举个例子:
debugger rescue nil
该函数是在代码中设置断点的一种相当不错的方法,但如果在调试器和 Rails 之外运行,则会引发异常。现在,从理论上讲,你不应该把调试代码留在你的程序中(pff!没有人这样做!),但你可能出于某种原因想把它保留一段时间,但不要继续运行你的调试器。debugger
注意:
如果您运行了其他人的程序来捕获信号异常并忽略它们,(例如上面的代码),那么:
- 在 Linux 中,在 shell 中键入 ,或 ,查找有问题的程序的 PID,然后运行 。
pgrep ruby
ps | grep ruby
kill -9 <PID>
- 在 Windows 中,使用任务管理器 (--),转到“进程”选项卡,找到您的进程,右键单击它并选择“结束进程”。CTRLSHIFTESC
- 在 Linux 中,在 shell 中键入 ,或 ,查找有问题的程序的 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
评论
ensure
rescue
The objectivity of the author of your quote is questionable
".这家伙写了 minitest 和大量其他广泛使用的宝石。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
哔哔
警告:捕获了 SyntaxError 异常。
信息:记录的错误 - 正在继续。
你注意到有些不对劲,你猛地按下了紧急休息键(:^C
Interrupt
)
哔哔
警告:捕获中断异常。
信息:记录的错误 - 正在继续。
是的 - 这没有多大帮助。你离铁轨很近,所以你把车停在停车场(ing:)。kill
SignalException
哔哔
警告:捕获 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
self.exceeding_safe_stopping_momentum?
rescue Exception
ensure
这篇博文完美地解释了这一点:Ruby 的异常与标准错误:有什么区别?
为什么你不应该拯救异常
拯救异常的问题 是它实际上拯救了继承自 例外。这是....所有的人!
这是一个问题,因为使用了一些例外 内部由 Ruby 提供。它们与您的应用程序没有任何关系,并且 吞下它们会导致坏事发生。
以下是一些重要的:
SignalException::Interrupt - 如果挽救此漏洞,则无法退出 应用程序通过按 Control-C 进行。
ScriptError::SyntaxError - 吞咽语法错误意味着事情 like puts(“忘记了什么)会悄无声息地失败。
NoMemoryError - 想知道当程序保持时会发生什么 在它用完所有 RAM 后运行?我也没有。
begin do_something() rescue Exception => e # Don't do this. This will swallow every single exception. Nothing gets past it. end
我猜你真的不想吞下这些东西 系统级异常。你只想抓住你所有的 应用程序级错误。异常导致了您的代码。
幸运的是,有一种简单的方法可以做到这一点。
改为救援 StandardError
应关注的所有异常都继承自 StandardError。这些是我们的老朋友:
NoMethodError - 尝试调用不存在的方法时引发
TypeError - 由 1 + “” 等原因引起
RuntimeError - 谁能忘记旧的 RuntimeError?
若要挽救此类错误,需要挽救 StandardError。你可以通过写这样的东西来做到这一点:
begin do_something() rescue StandardError => e # Only your app's exceptions are swallowed. Things like SyntaxErrror are left alone. end
但 Ruby 让它更容易使用。
当你根本没有指定异常类时,ruby 会假设你的意思是 StandardError。所以下面的代码与上面的代码是一样的:
begin do_something() rescue => e # This is the same as rescuing StandardError end
上一个:如何编写捕获所有异常的“try”/“except”块?
下一个:如何正确忽略异常
评论