为什么在Ruby中使用`rescue Exception => e`样式不好?

本文翻译自: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 . ExceptionRuby异常层次结构的根,因此,当您rescue Exception时, LoadError所有内容中抢救LoadError ,包括SyntaxErrorLoadErrorInterrupt等子类。

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 Ckill它来显示:

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: 但是,假设您有一台线程服务器,并且您不希望所有异常:

  1. be ignored (the default) 被忽略(默认)
  2. 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: 注意:

  1. 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 , or ps | grep ruby 在Linux的shell中,键入pgrep rubyps | grep ruby ps | grep ruby , look for your offending program's PID, and then run kill -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 ),转到“进程”选项卡,找到您的进程,右键单击它,然后选择“结束进程”。
  2. 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! 小心!

  3. 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 ) 您发现出了点问题,然后在紧急情况下大满贯( ^CInterrupt

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 ). 您非常靠近铁路,因此将汽车停在了停车场( killSignalException )。

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 (不要重新引发该异常)-否则您可能会驶离桥梁。

发布了0 篇原创文章 · 获赞 7 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/asdfgh0077/article/details/105356931
今日推荐