Начать, спасти и обеспечить в Ruby?

Недавно я начал программировать в Ruby, и я рассматриваю обработку исключений.

Мне было интересно, был ли ensure эквивалентом Ruby 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

Вызывается ли ensure независимо от того, что, даже если исключение не возникает?

451
задан ThePower 03 февр. '10 в 14:54
источник поделиться
7 ответов

Да, ensure гарантирует, что код всегда оценивается. Вот почему он назвал ensure. Таким образом, это эквивалентно Java и С# 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 unless filehandle.nil?
end

И что вы знаете: это уже доступно в основной библиотеке как File.open. Но это общий шаблон, который вы можете использовать и в своем собственном коде для реализации любого вида очистки ресурсов (à la using на С#) или транзакций или всего, что вы могли бы подумать.

Единственный случай, когда это не работает, если получение и освобождение ресурса распределяются по различным частям программы. Но если он локализован, как в вашем примере, вы можете легко использовать эти блоки ресурсов.


BTW: в современном С#, 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);
});
999
ответ дан Jörg W Mittag 03 февр. '10 в 16:04
источник поделиться

FYI, даже если в разделе rescue будет повторно задано исключение, блок ensure будет выполнен до того, как выполнение кода продолжит следующий обработчик исключений. Например:

begin
  raise "Error!!"
rescue
  puts "test1"
  raise # Reraise exception
ensure
  puts "Ensure block"
end
29
ответ дан alup 29 окт. '12 в 15:18
источник поделиться

Если вы хотите, чтобы файл был закрыт, вы должны использовать форму блока File.open:

File.open("myFile.txt", "w") do |file|
  begin
    file << "#{content} \n"
  rescue
  #handle the error here
  end
end
11
ответ дан Farrel 03 февр. '10 в 15:08
источник поделиться

Да, ensure вызывается при любых обстоятельствах. Для получения дополнительной информации см. "" Исключения, выхват и бросок " в книге" Программирование Ruby "и выполните поиск" обеспечить".

6
ответ дан Milan Novota 03 февр. '10 в 14:57
источник поделиться

Вот почему нам нужно ensure:

def hoge
  begin
    raise
  rescue  
    raise # raise again
  ensure  
    puts 'ensure' # will be executed
  end  
  puts 'end of func' # never be executed
end  
3
ответ дан kuboon 23 янв. '14 в 10:27
источник поделиться

Да, ensure как finally гарантирует, что блок будет выполнен. Это очень полезно для обеспечения защиты критических ресурсов, например. закрытие дескриптора файла при ошибке или освобождение мьютекса.

3
ответ дан Chris McCauley 03 февр. '10 в 15:09
источник поделиться

Да, ensure ENSURES запускается каждый раз, поэтому вам не нужно file.close в блоке begin.

Кстати, хороший способ проверить:

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

Вы можете проверить, будет ли "========== внутри гарантированного блока" распечатываться при возникновении исключения. Затем вы можете прокомментировать утверждение, которое вызывает ошибку, и посмотреть, выполняется ли оператор ensure, если будет распечатано что-либо.

3
ответ дан Aaron Qian 03 февр. '10 в 15:10
источник поделиться

Другие вопросы по меткам