Ruby异常——为什么;否则;?

Ruby异常——为什么;否则;?,ruby,exception-handling,Ruby,Exception Handling,我试图理解Ruby中的异常,但我有点困惑。我使用的教程说,如果发生的异常与rescue语句识别的任何异常都不匹配,则可以使用“else”来捕获它: begin # - rescue OneTypeOfException # - rescue AnotherTypeOfException # - else # Other exceptions ensure # Always will be executed end 但是,我在后面的教程中还看到,在使用“rescue”

我试图理解Ruby中的异常,但我有点困惑。我使用的教程说,如果发生的异常与rescue语句识别的任何异常都不匹配,则可以使用“else”来捕获它:

begin  
# -  
rescue OneTypeOfException  
# -  
rescue AnotherTypeOfException  
# -  
else  
# Other exceptions
ensure
# Always will be executed
end
但是,我在后面的教程中还看到,在使用“rescue”时没有指定异常:

begin
    file = open("/unexistant_file")
    if file
         puts "File opened successfully"
    end
rescue
    file = STDIN
end
print file, "==", STDIN, "\n"
如果你能做到这一点,那么我还需要用别的吗?或者我可以在最后像这样使用通用的救援工具吗

begin  
# -  
rescue OneTypeOfException  
# -  
rescue AnotherTypeOfException  
# -  
rescue
# Other exceptions
ensure
# Always will be executed
end

else
用于当块完成而未引发异常时。无论块是否成功完成,
确保运行
。例如:

begin
  puts "Hello, world!"
rescue
  puts "rescue"
else
  puts "else"
ensure
  puts "ensure"
end

这将打印
你好,世界
,然后
else
,然后
确保在开始救援结束块中的
else
块在您可能期望发生某种异常时使用。如果您运行了所有预期的异常,但仍然没有引发任何异常,那么在else块中,您可以执行任何需要的操作,因为您知道原始代码运行时没有错误。

下面是
begin
表达式中的
else
的具体用例。假设您正在编写自动测试,并且希望编写一个方法来返回由块引发的错误。但是,如果块没有引发错误,您也希望测试失败。您可以这样做:

def get_error_from(&block)
  begin
    block.call
  rescue => err
    err  # we want to return this
  else
    raise "No error was raised"
  end
end
请注意,您不能在
开始
块内移动
升高
,因为它将获得
救援
d。当然,还有其他不使用
else
的方法,比如在
结束后检查
err
是否为
nil
,但这并不简洁

就我个人而言,我很少以这种方式使用
else
,因为我认为它很少需要,但在那些罕见的情况下,它确实派上了用场

编辑

我想到了另一个用例。下面是一个典型的
开始
/
救援

begin
  do_something_that_may_raise_argument_error
  do_something_else_when_the_previous_line_doesnt_raise
rescue ArgumentError => e
  handle_the_error
end
为什么这不太理想?因为它的目的是当
做某件事可能会引起参数错误时
拯救
,而不是当
做某件事时,前一行没有引起

通常最好使用
begin
/
rescue
来包装您想要保护的最小代码,以防
上升
,否则:

  • 您可以屏蔽代码中不应该出现的bug
  • 救援的意图更难理解。有人(包括你未来的自己)可能会阅读代码并想知道“我想保护哪个表达式?它看起来像表达式ABC…但可能表达式DEF也一样???作者的意图是什么?!”重构变得更加困难
通过此简单的更改可以避免这些问题:

begin
  do_something_that_may_raise_argument_error
rescue ArgumentError => e
  handle_the_error
else
  do_something_else_when_the_previous_line_doesnt_raise
end

对于
else
块,我能看到的唯一原因是,如果您想在
块之前执行某些操作,请确保
开始
块中的代码没有引发任何错误

begin
  puts "Hello"
rescue
  puts "Error"
else
  puts "Success"
ensure
  puts "my old friend"
  puts "I've come to talk with you again."
end

多亏了
else
,您有时可以合并两个嵌套的
begin-end
块。
因此(我当前代码中的简化示例),而不是:

  begin
    html = begin
      NetHTTPUtils.request_data url
    rescue NetHTTPUtils::Error => e
      raise unless 503 == e.code
      sleep 60
      retry
    end
    redo unless html["market"]
  end
你写道:

  begin
    html = NetHTTPUtils.request_data url
  rescue NetHTTPUtils::Error => e
    raise unless 503 == e.code
    sleep 60
    retry
  else
    redo unless html["market"]
  end

“教程是什么,所以我知道不推荐它吗?”Andrewgrim在谷歌搜索了一下之后,我想提问者遵循的教程是。奖金WTF:该教程的作者似乎剽窃了第一个例子(在那里它已经是不正确的),但更糟糕的是,他把注释的缩进搞砸了。是的,我认为引导人们远离它是明智的——这是一个多么伟大的无能和不诚实的组合!以防有人遇到同样的问题。在“Ruby Way.Third Edition”中,作者指出,
begin
块中的
else
子句用于解救前面的
rescue
子句中未指定的类型的错误(基本上与本问题的教程所说的相同)。这是不正确的。这让我有一段时间感到困惑:在
begin/end
块中,
else
块仅在没有引发异常时运行。它不是“任何其他”例外的总括。对
else
的需求非常少……通常,在进行任何救援之前,您只需将正在进行的非异常代码放入主
begin
块。有关
else
的一些合法的深奥用法,请参见后面的答案。为什么要在begin块中包含else部分?@AntarrByrd在Ruby中,
begin
类似于其他语言中的
try
。这里的
else
意味着,如果在
begin
try
)块中没有抛出异常,则执行此操作。但是如果begin块中的代码没有抛出错误。您可以在那里继续,因为这是它将运行的唯一情况。@AntarrByrd有一个区别:异常处理程序将在
中被禁用(在
确保
之前仍在运行)。为什么您不能在
开始救援结束
块之后
引发“未引发错误”
?这不是和在
else
中执行完全相同吗?@Magne唯一有效的方法是,如果您也使用早期的
返回
,即
返回错误
,而不仅仅是
错误
。如果没有,则该方法无法返回块的错误,因为在该方法的末尾有一个无条件提升。我通常尽量避免提前/显式
返回
s(除了保护子句样式),因为它更容易遵循;这就是为什么
else
语法更吸引人的原因。啊,我没有注意到
begin-rescue-end
在一个要返回错误的方法中。但是,考虑我的问题,如果你不需要返回错误(例如,一个独立的代码>开始救援结束< /代码>),我猜如果你停止执行某件事,它将是相同的。