Ruby Exceptions – 为什么“else”?

Ruby Exceptions — Why “else”?

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

1
2
3
4
5
6
7
8
9
10
11
begin  
# -  
rescue OneTypeOfException  
# -  
rescue AnotherTypeOfException  
# -  
else  
# Other exceptions
ensure
# Always will be executed
end

不过,我在后面的教程中也看到了"rescue"的用法,没有指定任何异常:

1
2
3
4
5
6
7
8
9
10
begin
    file = open("/unexistant_file")
    if file
         puts"File opened successfully"
    end
rescue
    file = STDIN
end
print file,"==", STDIN,"
"

如果你能做到,那我还需要用其他的吗?或者我可以在最后像这样使用一个通用的救援吗?

1
2
3
4
5
6
7
8
9
10
11
begin  
# -  
rescue OneTypeOfException  
# -  
rescue AnotherTypeOfException  
# -  
rescue
# Other exceptions
ensure
# Always will be executed
end


else用于块在没有引发异常的情况下完成。无论块是否成功完成,都会运行ensure。例子:

1
2
3
4
5
6
7
8
9
begin
  puts"Hello, world!"
rescue
  puts"rescue"
else
  puts"else"
ensure
  puts"ensure"
end

这将打印Hello, world!,然后打印else,然后打印ensure


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

1
2
3
4
5
6
7
8
9
def get_error_from(&block)
  begin
    block.call
  rescue => err
    err  # we want to return this
  else
    raise"No error was raised"
  end
end

注意,你不能把raise移到begin块内,因为它会得到rescued。当然,还有其他不使用else的方法,比如检查errend之后是否是nil,但这并不是那么简单。

就我个人而言,我很少这样使用else,因为我认为这是很少需要的,但在那些罕见的情况下,它确实很有用。

编辑

我遇到了另一个用例。这里有一个典型的begin/rescue

1
2
3
4
5
6
begin
  do_something_that_may_raise_argument_error
  do_something_else_when_the_previous_line_doesnt_raise
rescue ArgumentError => e
  handle_the_error
end

为什么这不太理想?因为目的是当do_something_that_may_raise_argument_error提高ArgumentError时,而不是当do_something_else_when_the_previous_line_doesnt_raise提高时,rescue的目的。

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

  • 你可以在不应该出现在raise中的代码中隐藏错误。
  • rescue的意图很难理解。有人(包括你未来的自己)可能会读到这段代码,想知道"我想保护哪一个表达式?"看起来像是abc…但也许表达定义也一样?????作者的意图是什么?!"重构变得更加困难。

通过这个简单的更改,您可以避免这些问题:

1
2
3
4
5
6
7
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


begin rescue end块中的else块在您可能希望发生某种异常时使用。如果您运行了所有预期的异常,但仍然没有引发任何异常,那么在您的else块中,您可以做任何需要的事情,因为您知道原始代码运行无错误。


由于else,有时可以合并两个嵌套的begin end块。所以(我当前代码中的简化示例)而不是:

1
2
3
4
5
6
7
8
9
10
  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

你写:

1
2
3
4
5
6
7
8
9
  begin
    html = NetHTTPUtils.request_data url
  rescue NetHTTPUtils::Error => e
    raise unless 503 == e.code
    sleep 60
    retry
  else
    redo unless html["market"]
  end

对于else块,我能看到的唯一原因是,当begin块中的代码没有引发任何错误时,如果您想在ensure块之前执行某些操作。

1
2
3
4
5
6
7
8
9
10
begin
  puts"Hello"
rescue
  puts"Error"
else
  puts"Success"
ensure
  puts"my old friend"
  puts"I've come to talk with you again."
end