在Ruby中开始,Rescue and Ensure

Begin, Rescue and Ensure in Ruby?

我最近开始用Ruby编程,我正在研究异常处理。

我想知道,在C语言中,ensure是否与finally的红宝石等价?我应该有:

1
2
3
4
5
6
7
8
9
10
file = File.open("myFile.txt","w")

begin
  file <<"#{content}
"

rescue
  #handle the error here
ensure
  file.close unless file.nil?
end

或者我应该这样做?

1
2
3
4
5
6
7
8
9
10
11
12
#store the file
file = File.open("myFile.txt","w")

begin
  file <<"#{content}
"

  file.close
rescue
  #handle the error here
ensure
  file.close unless file.nil?
end

是否调用ensure,不管发生什么,即使没有引发异常?


是的,ensure确保始终对代码进行评估。这就是它被称为ensure的原因。因此,它相当于Java和C的EDCOX1〔3〕。

begin/rescue/else/ensure/end的总流量如下:

1
2
3
4
5
6
7
8
9
10
11
12
begin
  # something which might raise an exception
rescue SomeExceptionClass => some_variable
  # code that deals with some exception
rescue SomeOtherException => some_other_variable
  # code that deals with some other exception
else
  # code that runs only if *no* exception was raised
ensure
  # ensure that this code always runs, no matter what
  # does not change the final value of the block
end

您可以省去rescueensureelse。您还可以省略变量,在这种情况下,您将无法检查异常处理代码中的异常。(好吧,您可以始终使用全局异常变量来访问最后一个引发的异常,但这有点老生常谈。)您可以省略异常类,在这种情况下,将捕获从StandardError继承的所有异常。(请注意,这并不意味着所有的例外都会被捕获,因为有些例外是Exception的实例,而不是StandardError的实例。大多数严重的例外情况会影响程序的完整性,如SystemStackErrorNoMemoryErrorSecurityErrorNotImplementedErrorLoadErrorSyntaxErrorScriptErrorInterruptSignalExceptionSystemExit

一些块形成隐式异常块。例如,方法定义也是隐式的异常块,因此不是写入

1
2
3
4
5
6
7
def foo
  begin
    # ...
  rescue
    # ...
  end
end

你只写

1
2
3
4
5
def foo
  # ...
rescue
  # ...
end

1
2
3
4
5
def foo
  # ...
ensure
  # ...
end

这同样适用于class定义和module定义。

但是,在您询问的特定情况下,实际上有一个更好的成语。一般来说,当您处理一些最终需要清理的资源时,可以通过将块传递给一个为您进行所有清理的方法来完成。它类似于c中的using块,只是Ruby的功能非常强大,您不必等待微软的高级牧师从山上下来并为您优雅地更改他们的编译器。在Ruby中,您可以自己实现它:

1
2
3
4
5
6
7
8
9
10
11
# This is what you want to do:
File.open('myFile.txt', 'w') do |file|
  file.puts content
end

# And this is how you might implement it:
def File.open(filename, mode='r', perm=nil, opt=nil)
  yield filehandle = new(filename, mode, perm, opt)
ensure
  filehandle&.close
end

你知道什么:这已经在核心库中作为File.open提供了。但这是一种通用模式,您也可以在自己的代码中使用,用于实现任何类型的资源清理(C中的"la EDOCX1"(34))或事务或您可能想到的其他内容。

唯一不起作用的情况是,如果获取和释放资源分布在程序的不同部分。但是,如果它是本地化的,如您的示例中所示,那么您可以轻松地使用这些资源块。

顺便说一句:在现代C语言中,using实际上是多余的,因为您可以自己实现Ruby风格的资源块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class File
{
    static T open<T>(string filename, string mode, Func<File, T> block)
    {
        var handle = new File(filename, mode);
        try
        {
            return block(handle);
        }
        finally
        {
            handle.Dispose();
        }
    }
}

// Usage:

File.open("myFile.txt","w", (file) =>
{
    file.WriteLine(contents);
});


仅供参考,即使在rescue节中重新引发异常,在代码执行继续到下一个异常处理程序之前,将执行ensure块。例如:

1
2
3
4
5
6
7
8
begin
  raise"Error!!"
rescue
  puts"test1"
  raise # Reraise exception
ensure
  puts"Ensure block"
end


如果要确保关闭文件,应使用File.open的块形式:

1
2
3
4
5
6
7
8
File.open("myFile.txt","w") do |file|
  begin
    file <<"#{content}
"

  rescue
  #handle the error here
  end
end


是的,在任何情况下都会调用ensure。有关更多信息,请参阅Ruby编程手册的"异常、捕获和抛出",并搜索"确保"。


是的,ensure确保它每次都运行,因此您不需要begin块中的file.close

顺便说一下,测试的一个好方法是:

1
2
3
4
5
6
7
8
begin
  # Raise an error here
  raise"Error!!"
rescue
  #handle the error here
ensure
  p"=========inside ensure block"
end

您可以测试在出现异常时是否打印出"==inside-ensure block"。然后,您可以对引发错误的语句进行注释,并通过查看是否打印出任何内容来查看是否执行了ensure语句。


这就是我们需要ensure的原因:

1
2
3
4
5
6
7
8
9
10
def hoge
  begin
    raise
  rescue  
    raise # raise again
  ensure  
    puts 'ensure' # will be executed
  end  
  puts 'end of func' # never be executed
end


是的,像finally一样的ensure保证将执行该块。这对于确保关键资源得到保护非常有用,例如错误时关闭文件句柄或释放互斥体。