【问题标题】:Begin, Rescue and Ensure in Ruby?在 Ruby 中开始、拯救和确保?
【发布时间】:2011-01-12 14:15:31
【问题描述】:

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

我想知道 ensure 是否是 C# 中 finally 的 Ruby 等价物?我应该有:

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 无论如何都会被调用,即使没有引发异常?

【问题讨论】:

  • 两者都不好。通常,在处理外部资源时,您总是希望资源打开`位于 begin 块内。

标签: ruby-on-rails ruby exception exception-handling error-handling


【解决方案1】:

是的,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

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

一些块形成隐式异常块。例如,方法定义也是隐含的异常块,所以不要写

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

你只写

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

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

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

但是,在您询问的特定情况下,实际上有一个更好的习语。通常,当您使用一些最终需要清理的资源时,您可以通过将块传递给为您完成所有清理的方法来实现。它类似于 C# 中的 using 块,除了 Ruby 实际上足够强大,您不必等待微软的大祭司从山上下来并为您慷慨地更改他们的编译器。在 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 提供。但它是一种通用模式,您也可以在自己的代码中使用,以实现任何类型的资源清理(在 C# 中使用 using)或事务或您可能想到的任何其他内容。

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


顺便说一句:在现代 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);
});

【讨论】:

  • 请注意,虽然ensure语句最后执行,但它们并不是返回值。
  • 我喜欢在 SO 上看到这样丰富的贡献。它超出了 OP 的要求,因此它适用于更多的开发人员,但仍然是主题。我从这个答案+编辑中学到了一些东西。感谢您不只是写“是的,ensure 无论如何都会被调用。”
  • 请注意,不保证确保完成。以线程内部有开始/确保/结束的情况为例,然后在调用确保块的第一行时调用 Thread.kill 。这将导致其余的确保不执行。
  • @Teddy:确保保证开始执行,不保证完成。您的示例过于矫枉过正 - 确保块内的一个简单异常也会导致它退出。
  • 请注意,C# 示例并没有消除对using 的需要。 open 方法仍然需要进行清理。该示例只是以冗长(而不是 100% 防弹)的方式执行此操作,而不是使用 using 速记。我建议尽可能使用using 代替try-finally
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 我想如果你不想处理错误而只是提出它,并关闭文件句柄,你不需要在这里开始救援?
    【解决方案4】:

    是的,ensure 在任何情况下都会被调用。有关详细信息,请参阅 Programming Ruby 书籍的“Exceptions, Catch, and Throw”并搜索“ensure”。

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

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

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

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

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

        【讨论】:

          【解决方案7】:

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

          【讨论】:

          • 除非在他/她的情况下,不能保证文件会被关闭,因为File.open 部分不在 begin-ensure 块内。只有file.close 是不够的。
          猜你喜欢
          • 2013-06-12
          • 2013-06-28
          • 2012-07-31
          • 2010-10-07
          • 2010-12-15
          • 2013-11-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多