为什么在 Ruby 中`rescue Exception => e` 是不好的风格?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10048173/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-06 04:58:54  来源:igfitidea点击:

Why is it bad style to `rescue Exception => e` in Ruby?

rubyexception-handling

提问by John

Ryan Davis's Ruby QuickRefsays (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?

为什么不?正确的做法是什么?

回答by Andrew Marshall

TL;DR: Use StandardErrorinstead for general exception catching. When the original exception is re-raised (e.g. when rescuing to log the exception only), rescuing Exceptionis probably okay.

TL;DRStandardError用于一般异常捕获。当重新引发原始异常时(例如,当救援仅记录异常时),救援Exception可能没问题。



Exceptionis the root of Ruby's exception hierarchy, so when you rescue Exceptionyou rescue from everything, including subclasses such as SyntaxError, LoadError, and Interrupt.

Exception是根Ruby的异常层次结构,所以当你rescue Exception从拯救一切,包括子类,如SyntaxErrorLoadErrorInterrupt

Rescuing Interruptprevents the user from using CTRLCto exit the program.

RescuingInterrupt防止用户使用CTRLC退出程序。

Rescuing SignalExceptionprevents the program from responding correctly to signals. It will be unkillable except by kill -9.

Rescuing 会SignalException阻止程序正确响应信号。除了被kill -9.

Rescuing SyntaxErrormeans that evals that fail will do so silently.

RescuingSyntaxError意味着eval失败的 s 会默默地这样做。

All of these can be shown by running this program, and trying to CTRLCor killit:

所有这些都可以通过运行这个程序来显示,并尝试CTRLCkill它:

loop do
  begin
    sleep 1
    eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
  rescue Exception
    puts "I refuse to fail or be stopped!"
  end
end

Rescuing from Exceptionisn't even the default. Doing

救援Exception甚至不是默认设置。正在做

begin
  # iceberg!
rescue
  # lifeboats
end

does not rescue from Exception, it rescues from StandardError. You should generally specify something more specific than the default StandardError, but rescuing from Exceptionbroadensthe scope rather than narrowing it, and can have catastrophic results and make bug-hunting extremely difficult.

不救自Exception,它救自StandardError。您通常应该指定比 default 更具体的内容StandardError,但是从Exception扩大范围而不是缩小范围,可能会产生灾难性的结果并使错误搜索变得极其困难。



If you have a situation where you do want to rescue from StandardErrorand 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 Exceptionis 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

回答by Michael Slade

The realrule 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. 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).
  1. 被忽略(默认)
  2. 停止服务器(如果您说 ,就会发生这种情况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. Rails does this in its request handler.

以上适用于 Ruby 的默认异常处理程序的变体,其优点是它也不会杀死您的程序。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 notwant 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_somethingraises an exception, it is caught by Ruby, thrown away, and ais assigned "something else".

在这一行中,如果do_something引发异常,它会被 Ruby 捕获、丢弃并a分配给"something else"

Generally, don't do that, except in special cases where you knowyou don't need to worry. One example:

通常,不要这样做,除非在您知道不必担心的特殊情况下。一个例子:

debugger rescue nil

The debuggerfunction is a rather nice way to set a breakpoint in your code, but if running outside a debugger, and Rails, it raises an exception. 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.

debugger函数是在代码中设置断点的一种相当不错的方法,但是如果在调试器和 Rails 之外运行,则会引发异常。现在理论上你不应该在你的程序中留下调试代码(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, look for your offending program's PID, and then run 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".
  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:

    %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 cleanupwill be called every time, whether or not an exception is thrown.

  1. 如果您运行了其他人的程序来捕获信号异常并忽略它们,(比如上面的代码)那么:

    • 在 Linux 中,在 shell 中,键入pgrep rubyps | grep ruby,查找违规程序的 PID,然后运行kill -9 <PID>.
    • 在 Windows 中,使用任务管理器 ( CTRL- SHIFT- ESC),转到“进程”选项卡,找到您的进程,右键单击它并选择“结束进程”。
  2. 如果您正在使用其他人的程序,无论出于何种原因,该程序都充满了这些忽略异常块,那么将其放在主线的顶部是一种可能的解决方法:

    %W/INT QUIT TERM/.each { |sig| trap sig,"SYSTEM_DEFAULT" }
    

    这会导致程序通过立即终止、绕过异常处理程序来响应正常的终止信号, 而无需清除。因此它可能会导致数据丢失或类似情况。当心!

  3. 如果您需要这样做:

    begin
      do_something
    rescue Exception => e
      critical_cleanup
      raise
    end
    

    你实际上可以这样做:

    begin
      do_something
    ensure
      critical_cleanup
    end
    

    在第二种情况下,critical cleanup无论是否抛出异常,每次都会被调用。

回答by Ben Aubin

TL;DR

TL; 博士

Don't rescue Exception => e(and not re-raise the exception) - or you mightdrive off a bridge.

不要rescue Exception => e(也不要重新提出例外) - 否则你可能会开车离开桥。



Let's say you are in a car (running 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.

假设您在车上(运行 Ruby)。您最近安装了一个带有无线升级系统(使用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.

哎呀!这可能不太好?幸运的是,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.

Info: Logged Error - Continuing Process.

哔哔

警告:捕获 SyntaxError 异常。

信息:记录的错误 - 继续处理。

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 (killing: SignalException).

是的 - 这没有多大帮助。你离铁路很近,所以你把车停在了停车场(killing: SignalException)。

beep beep

Warning: Caught SignalException Exception.

Info: Logged Error - Continuing Process.

哔哔

警告:捕获 SignalException 异常。

信息:记录的错误 - 继续处理。

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. 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)

在最后一秒,你拔出钥匙(kill -9),以及汽车停止时,你踩住前进到方向盘(气囊不能膨胀,因为你没有正常停止程序-你终止它),计算机在你的汽车后部撞到它前面的座位上。一罐半满的可乐洒在纸上。后面的杂货都是压碎的,大部分都裹着蛋黄和牛奶。汽车需要认真修理和清洁。(数据丢失)

Hopefully you have insurance (Backups). Oh yeah - because the airbag didn't inflate, you're probably hurt (getting fired, etc).

希望你有保险(备份)。哦,是的 - 因为安全气囊没有充气,你可能会受伤(被解雇等)。



But wait! There's morereasons 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 Exceptiononly if you re-raise the exception. So, a better rule is to never swallow Exception, and always re-raise the error.

这是规则的例外情况:Exception只有在重新引发异常时才能捕获。因此,更好的规则是永远不要吞下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. And you do notwant to forget the raisestatement. And if you do, good luck trying to find that error.

但是在像 Ruby 这样的语言中添加救援很容易忘记,而且在重新提出问题之前添加救援语句感觉有点不干。而你想要忘记的raise声明。如果你这样做了,祝你好运找到那个错误。

Thankfully, Ruby is awesome, you can just use the ensurekeyword, which makes sure the code runs. The ensurekeyword 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).

值得庆幸的是,Ruby 很棒,您只需使用ensure关键字即可确保代码运行。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 => eis if you need access to the exception, or if you only want code to run on an exception. And remember to re-raise the error. Every time.

繁荣!无论如何,该代码应该运行。您应该使用的唯一原因rescue Exception => e是您是否需要访问异常,或者您是否只想在异常上运行代码。并记住重新提出错误。每次。

Note: As @Niall pointed out, ensure alwaysruns. 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.

注意:正如@Niall 所指出的,确保始终运行。这很好,因为有时您的程序可能会欺骗您并且不会抛出异常,即使出现问题也是如此。对于关键任务,例如给安全气囊充气,您需要确保它无论如何都会发生。正因为如此,每次汽车停下来检查,是否抛出异常,是一个好主意。尽管在大多数编程环境中充气安全气囊是一项不常见的任务,但这实际上在大多数清理任务中很常见。

回答by Sergio Tulentsev

Because this captures all exceptions. It's unlikely that your program can recover from anyof 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.

吞下异常是不好的,不要这样做。

回答by Russell Borogove

That's a specific case of the rule that you shouldn't catch anyexception 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.

这是规则的一个特殊情况,即您不应该捕获任何您不知道如何处理的异常。如果您不知道如何处理它,最好让系统的其他部分捕获并处理它。

回答by calebkm

I just read a great blog post about it at honeybadger.io:

我刚刚在Honeybadger.io 上阅读了一篇关于它的精彩博客文章:

Ruby's Exception vs StandardError: What's the difference?

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.

为什么你不应该拯救 Exception

拯救 Exception 的问题在于它实际上拯救了从 Exception 继承的每个异常。这是……全部!

这是一个问题,因为 Ruby 内部使用了一些异常。它们与您的应用程序没有任何关系,吞下它们会导致不好的事情发生。

以下是一些大的:

  • SignalException::Interrupt - 如果你解决这个问题,你不能通过点击 control-c 退出你的应用程序。

  • ScriptError::SyntaxError - 吞下语法错误意味着 puts("Forgot something) 之类的东西会默默地失败。

  • NoMemoryError - 想知道当您的程序在用完所有 RAM 后继续运行时会发生什么?我也不。

begin
  do_something()
rescue Exception => e
  # Don't do this. This will swallow every single exception. Nothing gets past it. 
end

我猜你不会真的想吞下这些系统级异常中的任何一个。您只想捕获所有应用程序级别的错误。异常导致您的代码。

幸运的是,有一个简单的方法可以做到这一点。