Исключения

Как и многие другие современные языки, 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? Для этой цели служит блок beginend.
В простейшей форме внутри него нет ничего, кроме кода:
Kbeginbegin
# Ничего полезного. '
# . . .
end
Просто перехватывать ошибки не очень осмысленно. Но у блока может быть
один или несколько обработчиков rescue. Если произойдет ошибка в любой точке
программы между begin и rescue, то управление сразу будет передано в подходящий
обработчик rescue.
begin
х = Math.sqrt(y/z)
# . . .
rescue ArgumentError
puts "Ошибка при извлечении квадратного корня."
rescue ZeroDivisionError
puts "Попытка деления на нуль."
end
Того же эффекта можно достичь следующим образом: д
begin
х = Math.sqrt(y/z)
# . . .
rescue => err
puts err
end
Здесь в переменной err хранится объект-исключение; при выводе ее на печать
объект будет преобразован в осмысленную символьную строку. Отметим, что
коль скоро тип ошибки не указан, то этот обработчик rescue будет перехватывать
все исключения, производные от класса StandardError. В конструкции rescue =>
variable можно перед символом => дополнительно указать тип ошибки.
Если типы ошибок указаны, то может случиться так, что тип реально возникшего
исключения не совпадает ни с одним из них. На этот случай после всех обработчиков
rescue разрешается поместить ветвь else,
begin
# Код, в котором может возникнуть ошибка...
rescue Typel
# ...
rescue Туре2
# ...
else
# Прочие исключения...
end
Часто мы хотим каким-то образом восстановиться после ошибки. В этом поможет
ключевое слово retry (внутри тела обработчика rescue). Оно позволяет
повторно войти в блок begin и попытаться еще раз выполнить операцию:
begin
# Код, в котором может возникнуть ошибка...
rescue
# Пытаемся восстановиться...
retry # Попробуем еще раз.
end
Наконец, иногда необходим код, который «подчищает» что-то после выполнения
блока begin-end. В этом случае можно добавить часть ensure:
begin
# Код, в котором может возникнуть ошибка...
rescue
# Обработка исключений,
ensure
# Этот код выполняется в любом случае,
end
Код, помещенный внутрь части ensure, выполняется при любом способе выхода
из блока begin-end - вне зависимости от того, произошло исключение или нет.
Исключения можно перехватывать еще двумя способами. Во-первых, существует
форма rescue в виде модификатора:
х = a/b rescue puts("Деление на нуль!")
Кроме того, тело определения метода представляет собой неявный блок beginend;
слово begin опущено, а все тело метода подготовлено к обработке исключения
и завершается словом end:
def some_method
# Код...
rescue
# Восстановление после ошибки...
end
На этом мы завершаем как обсуждение обработки исключений, так и рассмотрение
основ синтаксиса и семантики в целом.
У Ruby есть многочисленные аспекты, которых мы не коснулись. Оставшаяся
часть главы посвящена более развитым возможностям языка, в том числе рассмотрению
ряда практических приемов, которые помогут программисту среднего
уровня научиться «думать на Ruby».