Начать, спасти и убедиться в Ruby? - PullRequest
509 голосов
/ 03 февраля 2010

Я недавно начал программировать на 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 несмотря ни на что, даже если исключение не вызывается?

Ответы [ 7 ]

1117 голосов
/ 03 февраля 2010

Да, 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 в C #, за исключением того, что 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);
});
33 голосов
/ 29 октября 2012

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

begin
  raise "Error!!"
rescue
  puts "test1"
  raise # Reraise exception
ensure
  puts "Ensure block"
end
13 голосов
/ 03 февраля 2010

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

File.open("myFile.txt", "w") do |file|
  begin
    file << "#{content} \n"
  rescue
  #handle the error here
  end
end
6 голосов
/ 03 февраля 2010

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

4 голосов
/ 03 февраля 2010

Да, ensure ОБЕСПЕЧИВАЕТ, что он запускается каждый раз, поэтому вам не нужно file.close в блоке begin.

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

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

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

3 голосов
/ 23 января 2014

Вот почему нам нужно 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 голосов
/ 03 февраля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...