Недавно я начал программировать в 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
независимо от того, что, даже если исключение не возникает?
Да, 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);
});
ensure
выполняются последними, они не являются возвращаемым значением.
begin ... rescue Exception => e ... puts e.message; puts e.backtrace.inspect ... end
для отображения всех типов исключений, выполнив следующее: begin ... rescue Exception => e ... puts e.message; puts e.backtrace.inspect ... end
.
FYI, даже если в разделе rescue
будет повторно задано исключение, блок ensure
будет выполнен до того, как выполнение кода продолжит следующий обработчик исключений. Например:
begin
raise "Error!!"
rescue
puts "test1"
raise # Reraise exception
ensure
puts "Ensure block"
end
Если вы хотите, чтобы файл был закрыт, вы должны использовать форму блока File.open
:
File.open("myFile.txt", "w") do |file|
begin
file << "#{content} \n"
rescue
#handle the error here
end
end
Да, ensure
вызывается при любых обстоятельствах. Для получения дополнительной информации см. "" Исключения, выхват и бросок " в книге" Программирование Ruby "и выполните поиск" обеспечить".
Вот почему нам нужно ensure
:
def hoge
begin
raise
rescue
raise # raise again
ensure
puts 'ensure' # will be executed
end
puts 'end of func' # never be executed
end
Да, ensure
ENSURES запускается каждый раз, поэтому вам не нужно file.close
в блоке begin
.
Кстати, хороший способ проверить:
begin
# Raise an error here
raise "Error!!"
rescue
#handle the error here
ensure
p "=========inside ensure block"
end
Вы можете проверить, будет ли "========== внутри гарантированного блока" распечатываться при возникновении исключения.
Затем вы можете прокомментировать утверждение, которое вызывает ошибку, и посмотреть, выполняется ли оператор ensure
, если будет распечатано что-либо.
Да, ensure
как finally
гарантирует, что блок будет выполнен. Это очень полезно для обеспечения защиты критических ресурсов, например. закрытие дескриптора файла при ошибке или освобождение мьютекса.
File.open
часть File.open
НЕ находится внутри блока begin-обеспечить. Только file.close
есть, но этого недостаточно.
begin
.