Перейдем к простому примеру, в котором мы будем обрабатывать исключительную ситуацию «деление на ноль». При запуске данной программы возникает исключительная ситуация (ZeroDivisionError).
Для реализации механизма исключений в языке следующие ключевые слова: rescue, raise, ensure.
• rescue – поймать и обработать исключение, находящееся в блоке.
• raise – генерировать исключительную ситуацию.
• ensure – всегда выполнить код, заключенный в этот блок.
rescue ZeroDivisionError
puts "Произошло деление на ноль"
ensure
puts "Блок ensure всегда выполняется!"
end
Обработаем ситуацию, описанную выше, внеся опасный участок кода в блок обработки исключения rescue.
Если кроме блока rescue присутствует блок ensure, то он будет выполнен в любом случае. Поэтому зачастую блок ensure используют для освобождения зарезервированных ресурсов. В следующей программе блок ensure следит за освобождением дескриптора файла.
f = File.open("testfile") begin #.. выполнение rescue #.. обработка ошибки ensure # закрыть соединение с файлом f.close unless f.nil? end
Будьте внимательны при использовании исключений. Использование конструкции rescue без указания конкретного исключения приводит к обработке всех ошибок.
Любая библиотека Ruby возбуждает исключения при возникновении любой ошибки, и вы также можете явно возбуждать ошибки в своем коде. Создать исключение программист может с помощью метода raise модуля Kernel (или его синонима fail). Рассмотрим его применение на следующих примерах:
raise
raise "Missing name" if name.nil?
if i >= names.size
raise IndexError, "#{i} >= size(#{names.size})"
end
Первая форма просто перехватывает текущее исключение и позволяет тем или иным способом затем обработать его. Старайтесь не использовать форму возбуждения исключения без аргумента, так как она не информативна. Второй пример демонстрирует передачу сообщения при возбуждении исключения. Вид возбужденного исключения в этом случае можно узнать, определив тип переменной $!
sum=0
begin
a.each{|x|sum+=1}
rescue
puts sum
end
Обратите внимание на следующую программу. Она не должна работать, т.к. объект а не проинициализирован, но …
В некоторых ситуациях программист не может сказать, когда закончится процесс ввода данных. Например, на вход поступает некая последовательность чисел. При завершении ввода требуется выдать какую-нибудь характеристику последовательности. В такой систуации уместно использовать обработку исключений (конкретно, ситуацию ЕОРЕггог). Начнем с ряда простых примеров.
sum=0
begin
while true
sum+=readline.to_f
end
rescue EOFError
puts "Сумма последовательности #{sum}"
end
Сумма элементов последовательности
Пусть в задаче требуется вычислить сумму всех элементов последовательности чисел, получаемых из входного потока. Мы вводим данные в бесконечном цикле и при получении нового числа прибавляем его значение к переменной sum. При завершении ввода печа тается значение переменной sum. (При вводе даннных с консоли нажатие Ctrl-D (CTRL-Z в Windows) завершает ввод.)
Обратим внимание на распространенную ошибку. При работе с последовательностью не следует использовать контейнеры (объекты классов Array или Hash) для ее хранения, так как мы считаем, что элементов может быть бесконечно много и памяти для хранения недостаточно. Следующая программа может аварийно завершиться, если входная последовательность содержит большое число членов.
arr=[] #Не последовательность
begin
arr << readline.to_iwhiletrue
rescue EOFError
sum=0
arr.each{|v|sum+=v}
puts sum
end
Похожая на readline функция gets не возбуждает исключительной ситуации при прерывании ввода и, по этой причине, не может использоваться в задачах, требующих обработки исключений.
Для решения этой задачи нам недостаточно одной переменной sum, так как функция, вычисляющая среднее арифметическое значение последовательности, не является индуктивной. Потребуется построить её индуктивное расширение. Для одной и той же функции f можно придумать разные расширения. Однако, наибольший практический интерес представляет такое индуктивное расширение, которое содержит минимум дополнительной информации.