Начать, спасти и убедиться в Ruby?

429

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

  • 1
    Ни то, ни другое Как правило, при работе с внешними ресурсами вы всегда хотите, чтобы открытие ресурса находилось внутри блока begin .
Теги:
exception
exception-handling
error-handling

7 ответов

1048
Лучший ответ

Да, 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);
});
  • 70
    Обратите внимание, что хотя операторы ensure выполняются последними, они не являются возвращаемым значением.
  • 0
    Хочу добавить, что вы можете иметь begin ... rescue Exception => e ... puts e.message; puts e.backtrace.inspect ... end для отображения всех типов исключений, выполнив следующее: begin ... rescue Exception => e ... puts e.message; puts e.backtrace.inspect ... end .
Показать ещё 10 комментариев
29

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

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

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

File.open("myFile.txt", "w") do |file|
  begin
    file << "#{content} \n"
  rescue
  #handle the error here
  end
end
  • 3
    Я думаю, если вы не хотите обрабатывать ошибку, а просто поднять ее и закрыть дескриптор файла, вам не нужно начинать спасение здесь?
6

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

3

Вот почему нам нужно 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

Да, 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

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

  • 0
    За исключением его / ее случая, нет никакой гарантии, что файл будет закрыт, потому File.open часть File.open НЕ находится внутри блока begin-обеспечить. Только file.close есть, но этого недостаточно.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню