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


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

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

 

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

 

 

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

Рассмотрим проблему написания функции quasi_inverse , возвращающей для каждого вещественного x обратную величину 1/x или 0 , если x слишком мало.

Подобные задачи по существу нельзя реализовать, не используя механизм исключений. Единственный практичный способ узнать, можно ли для данного x получить обратную величину, это выполнить деление. Но деление может спровоцировать переполнение, и если нет механизма управления исключениями, то программа завершится отказом, и будет слишком поздно возвращать 0 в качестве результата.

На некоторых платформах можно написать функцию invertible , такую что invertible(x) равна true , если и только если обратная величина может быть вычислена. Тогда можно написать и quasi_inverse . Но это решение не будет переносимым, и может приводить к потере производительности при интенсивном использовании этой функции.Механизм rescue-retry позволяет просто решить эту проблему, по крайней мере, на платформе, включающей сигнал при арифметическом переполнении:

 

quasi_inverse (x: REAL): REAL is

-- 1/x, если возможно, иначе 0

local

division_tried: BOOLEAN

do

if not division_tried then

Result := 1/x

end

rescue

division_tried := True

retry

end

 

 

Правила инициализации устанавливают значение false для division_tried в начале каждого вызова. В теле не нужно предложение else , поскольку инициализация установит Result равным 0 .