1. ホーム
  2. ruby-on-rails

[解決済み] RubyでBegin, Rescue, Ensure?

2022-03-16 11:47:31

質問

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

と思っていたら ensure はRubyの 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 は、例外が発生しなくても、何があっても呼び出されるのでしょうか?

解決方法は?

はい。 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

を省くことができます。 rescue , ensure または else . また、変数を省略することもできますが、その場合、例外処理コードで例外を検査することができなくなります。(グローバル例外変数を使って、最後に発生した例外にアクセスすることは可能ですが、ちょっと面倒です)。また、例外クラスを省略することもできます。 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

以下も同様です。 class の定義と module の定義に従います。

しかし、ご質問の具体的なケースでは、実はもっと良いイディオムがあるのです。一般に、あるリソースを扱うときに、最後にクリーンアップする必要がある場合、クリーンアップをすべて行ってくれるメソッドにブロックを渡すことでそれを行うことができます。これは 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 . しかし、これはあなた自身のコードにも使える一般的なパターンであり、あらゆる種類のリソースのクリーンアップを実装することができます(à la 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);
});