Выбрать главу

integrate

solve (from GENERAL_EQUATION)

filter

interval_big_enough: Нарушено предусловие

Отказ программы

Отказ программы

Отказ

Отказ

Повторение

O2

O1(root)

MATH

INTERFACE

new_matrix (from BASIC_MATH)

make

enough_memory: Check Нарушение

Отказ программы

Отказ

Отказ

Таблица 12.1.Пример таблицы истории исключений

Эта таблица содержит историю не только тех исключений, которые привели, в конечном счете, к отказу системы, но и исключений, эффект которых был преодолен в результате выполнения rescue - retry. Число исключений в таблице может быть ограничено, например, числом 100 по умолчанию. Порядок в таблице сверху вниз является обратным порядку, в котором вызываются программы. Корневая процедура создания записана в последней строке таблицы.

Столбец Программа идентифицирует для каждого исключения программу, чей вызов был прерван исключением. Столбец Объект идентифицирует цель этого вызова; используемые здесь имена O1 и так далее, но в реальной трассировке они будут внутренними идентификаторами, позволяющие определить, являются ли объекты совпадающими. Столбец Класс указывает класс, генерирующий объект.

Столбец Природа Исключения указывает, что случилось. Здесь, как показано во второй сверху строке таблицы, могут использоваться метки утверждений, например, interval_big_enough, что позволяет точно идентифицировать нарушаемое предложение в программе.

Последний столбец указывает, как обрабатывалось исключение, то ли используя Повторение, то ли Отказ. Таблица состоит из последовательности секций, отделяемых толстой линией. Каждая секция, за исключением последней, приводила к Повторению, что указывает на восстановление ситуации. Понятно, что между двумя вызовами, отделенными толстыми линиями, может быть произвольное число вызовов.

Игнорируя такие промежуточные вызовы, - успешные и потому неинтересные для цели нашего обсуждения - здесь приведена цепочка вызовов и возвратов, соответствующая выше приведенной истории исключений. Для реконструкции действий следует следовать по стрелкам, обходя их против часовой стрелки, начиная от программы make, изображенной слева вверху.

Рис. 12.2.  Выполнение, приведшее к отказу

Примеры обработки исключений

Теперь, когда у нас есть базисный механизм, давайте посмотрим, как он применяется в общих ситуациях.

Поломки при вводе

Предположим, что в интерактивной системе необходимо выдать подсказку пользователю, от которого требуется ввести целое. Пусть только одна процедура занимается вводом целых - read_one_integer, которая результат ввода присваивает атрибуту last_integer_read. Эта процедура работает неустойчиво, - если на ее входе будет нечто, отличное от целого, она может привести к отказу, выбрасывая исключение. Конечно, вы не хотите, чтобы это событие приводило к отказу всей системы. Но поскольку вы не управляете программой ввода, то следует ее использовать и организовать восстановление ситуации, при возникновении исключений. Вот возможная схема:

get_integer is

-- Получить целое от пользователя и сделать его доступным в

-- last_integer_read.

-- Если ввод некорректен, запросить повторения, столько раз,

-- сколько необходимо.

do

print ("Пожалуйста, введите целое: ")

read_one_integer

rescue

retry

end

Эта версия программы иллюстрирует стратегию повторения.

Очевидный недостаток - пользователь упорно вводит ошибочное значение, программа упорно запрашивает значение. Это не очень хорошее решение. Можно ввести верхнюю границу, скажем 5, числа попыток. Вот пересмотренная версия:

Maximum_attempts: INTEGER is 5

-- Число попыток, допустимых при вводе целого.

get_integer is

-- Попытка чтения целого, делая максимум Maximum_attempts попыток.

-- Установить значение integer_was_read в true или false

-- в зависимости от успеха чтения.

-- При успехе сделать целое доступным в last_integer_read.

local

attempts: INTEGER

do

if attempts < Maximum_attempts then

print ("Пожалуйста, введите целое: ")

read_one_integer

integer_was_read := True

else

integer_was_read := False

end

rescue

attempts := attempts + 1

retry

end

Предполагается, что включающий класс имеет булев атрибут integer_was_read.

Вызывающая программа должна использовать эту программу следующим образом, пытаясь введенное целое присвоить сущности n:

get_integer

if integer_was_read then

n := last_integer_read

else

"Иметь дело со случаем, в котором невозможно получить целое"

end

Восстановление при исключениях, сгенерированных операционной системой

Среди событий, включающих исключения, есть сигналы, посылаемые операционной системой, некоторые из которых являются следствием аппаратных прерываний. Примеры: арифметическое переполнение сверху и снизу, невозможные операции ввода-вывода, запрещенные команды, обращение к недоступной памяти, прерывания от пользователя (например, нажата клавиша break).

Теоретически можно рассматривать такие условия, как нарушение утверждений. Если a+b приводит к переполнению, то это означает, что вызов не удовлетворяет неявному предусловию функции + для целых или вещественных чисел, устанавливающее, что сумма двух чисел должна быть представима в компьютере. Подобное неявное предусловие задается при создании новых объектов (создание копии) - памяти должно быть достаточно. Отказы встречаются из-за того, что окружение - файлы, устройства, пользователи - не отвечают условиям применимости. Но в таких случаях непрактично или невозможно задавать утверждения, допуская их независимую проверку. Единственное решение - пытаться выполнить операцию, и, если аппаратура или операционная система выдает сигнал о ненормальном состоянии, рассматривать его как исключение.