开始,救援和确保使用Ruby?


547

我最近开始使用Ruby进行编程,并且正在研究异常处理。

我想知道ensureRuby是否等效finally于C#?我应该有:

file = File.open("myFile.txt", "w")

begin
  file << "#{content} \n"
rescue
  #handle the error here
ensure
  file.close unless file.nil?
end

还是我应该这样做?

#store the file
file = File.open("myFile.txt", "w")

begin
  file << "#{content} \n"
  file.close
rescue
  #handle the error here
ensure
  file.close unless file.nil?
end

不会ensure得到所谓不管,即使一个异常没有什么引发,?


1
都不是好事。通常,在处理外部资源时,您始终希望资源开放位于begin块内部。
Nowaker

Answers:


1181

是的,请ensure确保始终对代码进行评估。这就是为什么将其称为ensure。因此,它等效于Java和C#finally

的一般流程begin/ rescue/ else/ ensure/ end如下所示:

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

您可以省去rescueensure或者else。您也可以省略变量,在这种情况下,您将无法在异常处理代码中检查异常。(好吧,您始终可以使用全局异常变量来访问最后一个引发的异常,但这有点棘手。)并且可以省去异常类,在这种情况下,所有继承自StandardError将捕获。(请注意,这并不意味着所有的异常被捕获,因为有例外,有实例Exception,但不是StandardError。可能有非常严重的异常妥协方案,如诚信SystemStackErrorNoMemoryErrorSecurityErrorNotImplementedErrorLoadErrorSyntaxErrorScriptErrorInterruptSignalExceptionSystemExit。)

一些块形成隐式异常块。例如,方法定义也隐式地也是异常块,因此与其编写

def foo
  begin
    # ...
  rescue
    # ...
  end
end

你只写

def foo
  # ...
rescue
  # ...
end

要么

def foo
  # ...
ensure
  # ...
end

class定义和module定义也相同。

但是,在您要询问的特定情况下,实际上有一个更好的习惯用法。通常,当您处理需要最终清理的某些资源时,可以通过将一个块传递给为您完成所有清理工作的方法来实现。它与usingC#中的代码块相似,不同之处在于Ruby实际上足够强大,您不必等待Microsoft的大祭司从山上下来就可以为您更改编译器。在Ruby中,您可以自己实现:

# 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。但这是一种通用模式,您也可以在自己的代码中使用该模式来实现任何类型的资源清除(using在C#中为“ 单调” )或事务或您可能想到的任何其他方式。

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


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

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);
});

81
请注意,尽管ensure语句最后执行,但它们不是返回值。
克里斯,

30
我喜欢看到SO这样的丰富贡献。它超越了OP的要求,因此它适用于更多开发人员,但仍然是热门话题。我从这个答案和编辑中学到了一些东西。谢谢您不仅写了“是的,ensure无论如何都会被调用”。
丹尼斯2014年

3
请注意,不能保证一定要完成。以在线程内部具有开始/确保/结束的情况为例,然后在调用suresure块的第一行时调用Thread.kill。这将导致其余的确保无法执行。
Teddy 2015年

5
@Teddy:确保保证开始执行,而不保证完成。您的示例是过大的杀手-sure块中的一个简单异常也会导致它退出。
Martin Konecny 2015年

3
还要注意,没有保证可以确保被调用。我是认真的。可能会发生断电/硬件错误/操作系统崩溃,并且如果您的软件很重要,则也需要考虑这一点。
EdvardM 2015年

37

仅供参考,即使在本rescue节中再次引发了异常,ensure也会在代码执行继续到下一个异常处理程序之前执行该块。例如:

begin
  raise "Error!!"
rescue
  puts "test1"
  raise # Reraise exception
ensure
  puts "Ensure block"
end

14

如果要确保关闭文件,则应使用以下块形式File.open

File.open("myFile.txt", "w") do |file|
  begin
    file << "#{content} \n"
  rescue
  #handle the error here
  end
end

3
我想如果您不想处理该错误,而只是提出该错误,然后关闭文件句柄,就不需要在这里进行抢救了吗?
rogerdpack


5

是的,ensure确保每次都运行它,所以您不需要file.closebegin块中使用。

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

begin
  # Raise an error here
  raise "Error!!"
rescue
  #handle the error here
ensure
  p "=========inside ensure block"
end

您可以测试是否在出现异常时是否将“ ========= inside sure block”打印出来。然后,您可以注释掉引发错误的语句,并ensure通过查看是否打印出任何内容来查看该语句是否已执行。


4

这就是为什么我们需要ensure

def hoge
  begin
    raise
  rescue  
    raise # raise again
  ensure  
    puts 'ensure' # will be executed
  end  
  puts 'end of func' # never be executed
end  

4

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


除了他/她的情况外,不能保证文件会关闭,因为File.openpart 不在begin-ensure块内。仅file.close是,但还不够。
Nowaker
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.