Отметим, что вместо фигурных скобок, в которые заключен блок, можно написать ключевые слова
do
и
end
. Различия между этими формами есть, но довольно тонкие.
1.2.7. Исключения
Как и многие другие современные
языки, Ruby поддерживает исключения.
Исключения — это механизм обработки ошибок, имеющий существенные преимущества по сравнения с прежними подходами. Нам удается избежать возврата кодов ошибок и запутанной логики их анализа, а код, который обнаруживает ошибку, можно отделить от кода, который ее обрабатывает (чаще всего они так или иначе разделены).
Предложение
raise
возбуждает исключение. Отметим, что
raise
— не зарезервированное слово, а метод модуля
Kernel
. (У него есть синоним
fail
.)
raise # Пример 1
raise "Произошла ошибка" # Пример 2
raise ArgumentError # Пример 3
raise ArgumentError, "Неверные данные" # Пример 4
raise ArgumentError.new("Неверные данные ") # Пример 5
raise ArgumentError, " Неверные данные ", caller[0] # Пример 6
В первом примере повторно возбуждается последнее встретившееся исключение. В примере 2 создается исключение
RuntimeError
(подразумеваемый тип), которому передается сообщение
"Произошла ошибка"
.
В примере 3 возбуждается исключение типа
ArgumentError
, а в примере 4 такое же исключение, но с сообщением
"Неверные данные"
. Пример 5 — просто другая запись примера 4. Наконец, в примере 6 еще добавляется трассировочная информация вида
"filename:line"
или
"filename:line:in 'method'"
(хранящаяся в массиве
caller
).
А как обрабатываются исключения в Ruby? Для этой цели служит блок
begin-end
. В простейшей форме внутри него нет ничего, кроме кода:
begin
#Ничего полезного.
#...
end
Просто перехватывать ошибки не очень осмысленно. Но у блока может быть один или несколько обработчиков
rescue
. Если произойдет ошибка в любой точке программы между
begin
и
rescue
, то управление сразу будет передано в подходящий обработчик
rescue
.
begin
x = Math.sqrt(y/z)
# ...
rescue ArgumentError
puts "Ошибка при извлечении квадратного корня."
rescue ZeroDivisionError
puts "Попытка деления на нуль."
end
Того же эффекта можно достичь следующим образом:
begin
x = Math.sqrt(y/z)
# ...
rescue => err
puts err
end
Здесь
в переменной
err
хранится объект-исключение; при выводе ее на печать объект будет преобразован в осмысленную символьную строку. Отметим, что коль скоро тип ошибки не указан, то этот обработчик
rescue
будет перехватывать все исключения, производные от класса S
tandardError
. В конструкции
rescue => variable
можно перед символом
=>
дополнительно указать тип ошибки.
Если типы ошибок указаны, то может случиться так, что тип реально возникшего исключения не совпадает ни с одним из них. На этот случай после всех обработчиков
rescue
разрешается поместить ветвь
else
.
begin
# Код, в котором может возникнуть ошибка...
rescue Type1
# ...
rescue Type2
# ...
else
#Прочие исключения...
end
Часто мы хотим каким-то образом восстановиться после ошибки. В этом поможет ключевое слово
retry
(внутри тела обработчика
rescue
). Оно позволяет повторно войти в блок
begin
и попытаться еще раз выполнить операцию:
begin
# Код, в котором может возникнуть ошибка...
rescue
# Пытаемся восстановиться...
retry # Попробуем еще раз.
end
Наконец, иногда необходим код, который «подчищает» что-то после выполнения блока
begin-end
. В этом случае можно добавить часть
ensure
:
begin
# Код, в котором может возникнуть ошибка...
rescue
# Обработка исключений.
ensure
# Этот код выполняется в любом случае.
end
Код, помещенный внутрь части
ensure
, выполняется при любом способе выхода из блока
begin-end
— вне зависимости от того, произошло исключение или нет.
Исключения можно перехватывать еще двумя способами. Во-первых, существует форма
rescue
в виде модификатора:
x = a/b rescue puts("Деление на нуль!")
Кроме того, тело определения метода представляет собой неявный блок
begin-end
; слово
begin
опущено, а все тело метода подготовлено к обработке исключения и завершается словом