Techioz Blog

RubyでBegin、Rescue、Ensure?

概要

最近 Ruby でプログラミングを始めて、例外処理について調べています。

RubyのensureはC#のfinallyに相当するのか疑問に思っていました。持つべきもの:

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

たとえ例外が発生しなくても、何があっても確実に呼び出されますか?

解決策

はい、コードが常に評価されるようにします。それが「保証」と呼ばれる理由です。つまり、最終的には Java や C# と同等になります。

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

レスキュー、確保、その他を省略することもできます。変数を省略することもできます。その場合、例外処理コードで例外を検査できなくなります。 (グローバル例外変数を使用すると、最後に発生した例外にアクセスすることはいつでもできますが、これは少し裏技的です。) また、例外クラスを省略することもできます。その場合、StandardError から継承するすべての例外がキャッチされます。 (これは、すべての例外がキャッチされるという意味ではないことに注意してください。Exception のインスタンスではあるが StandardError ではない例外も存在するためです。ほとんどの場合、SystemStackError、NoMemoryError、SecurityError、NotImplementedError、LoadError、 SyntaxError、ScriptError、Interrupt、SignalException、または SystemExit。)

一部のブロックは暗黙的な例外ブロックを形成します。たとえば、メソッド定義は暗黙的に例外ブロックでもあるため、次のように記述する代わりに

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

あなたはただ書きます

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

または

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

クラス定義とモジュール定義にも同じことが当てはまります。

ただし、あなたが質問している特定のケースでは、実際にはもっと優れたイディオムがあります。一般に、最後にクリーンアップする必要があるリソースを操作する場合、すべてのクリーンアップを行うメソッドにブロックを渡すことによってそれを行います。これは C# の using ブロックに似ていますが、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 として利用可能です。ただし、これは、あらゆる種類のリソース クリーンアップ (C# で使用するもの) やトランザクション、その他考えられるあらゆるものを実装するために、独自のコードでも使用できる一般的なパターンです。

これが機能しない唯一のケースは、リソースの取得と解放がプログラムの異なる部分に分散されている場合です。ただし、例のようにローカライズされている場合は、これらのリソース ブロックを簡単に使用できます。

ところで、最新の C# では、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);
});