logo
Программирование на языке Ruby

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 будет перехватывать все исключения, производные от класса StandardError. В конструкции 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 опущено, а все тело метода подготовлено к обработке исключения и завершается словом end:

def some_method

 # Код...

rescue

 # Восстановление после ошибки...

end

На этом мы завершаем как обсуждение обработки исключений, так и рассмотрение основ синтаксиса и семантики в целом.

У Ruby есть многочисленные аспекты, которых мы не коснулись. Оставшаяся часть главы посвящена более развитым возможностям языка, в том числе рассмотрению ряда практических приемов, которые помогут программисту среднего уровня научиться «думать на Ruby».