В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 34
Текст из файла (страница 34)
рассматривать и особенности взаимодействия асинхронных процессов, и перебор
с возвратом (backtracking), и вызов процедур по образцу, и поведение
экспертных систем, и вообще программирование достаточно высоко
интеллектуальных исполнителей (в том числе программирование человеческой
деятельности).
Связь с этим аспектом мира исполнителя вступает в действие при
исполнении любой программы, как только возникает исключительная ситуация.
Вместе с тем в развитом ЯП всегда имеются и специальные средства
конкретизации, позволяющие корректировать априорные правила поведения с
учетом потребностей конкретной программы.
Важно понимать, что без априорных правил поведения исполнителя не
обойтись. Ведь если программист захочет все предусмотреть, он должен
планировать проверку соответствующих условий. Но исключительные ситуации
могут возникнуть в процессе исполнения программы проверки! Так что чисто
программным путем задачу не решить. Необходим определенный исходный
(априорный) уровень подготовки исполнителя.
В жизни примерами "общих правил" служат обязанность вызвать пожарную
команду по телефону 01, милицию - по 02, скорую помощь - по 03, а также
выполнить инструкцию по оказанию неотложной помощи пострадавшим,
мобилизационное предписание и т.п.
Пример конкретизации - подробный план эвакуации людей из помещения при
пожаре, план мобилизационных и эвакуационных мероприятий на случай войны,
перечень занятий дежурного при отсутствии телефонных звонков (для него
обычная работа - отвечать на телефонные звонки, а их отсутствие -
исключение).
Принцип минимальных возмущений: затраты на учет чрезвычайных
обстоятельств должны быть по возможности минимальными (при гарантии
сохранения жизнеспособности системы). Этот же принцип в терминах ЯП:
языковые средства должны быть такими, чтобы забота об исключениях в
минимальной возможной степени сказывалась на всех этапах жизненного цикла
программных сегментов, реализующих основную содержательную функцию
программы.
Другими словами, аппарат исключений в ЯП должен быть таким, чтобы
программирование поведения в чрезвычайных обстоятельствах могло быть в
максимально возможной степени отделено от программирования основной функции
(в частности, не мешало понимать содержательную функцию программы),
накладные расходы на исполнение основной функции могли быть минимальными и
т.п.
Принцип минимальных повреждений: ущерб при возникновении исключений
должен быть минимальным. Речь идет уже не о минимизации затрат на саму
способность реагировать на исключения, а об ущербе (иногда принципиально
неустранимом) который может быть нанесен при реальном возникновении
чрезвычайных для программы обстоятельств.
Например, разумный аппарат исключений должен предусматривать
возможность реагировать на исключение как можно раньше и как можно точнее,
чтобы предотвратить дальнейшее разрушение программ и данных при аварийном их
функционировании. Ясно, что это принцип имеет смысл только тогда, когда
исключения трактуются как аварии или реально оказываются таковыми (часто это
как раз априорные исключения). Например, завершение файла при чтении часто
удобно считать исключением с точки зрения "нормальной" обработки его
записей, однако в этом случае не имеет смысла говорить об авариях или
повреждениях.
Полезные примеры, помогающие лучше прочувствовать суть изложенных
принципов, содержатся в [14].
8.3. Аппарат исключений в ЯП
Концепция исключения в ЯП содержательно имеет много общего с
концепцией аппаратного внутреннего прерывания, однако могут быть и
существенные отличия. Ближе всего к понятию прерывания трактовка исключений
в языке ПЛ/1.
Выделим четыре аспекта аппарата исключений:
определение исключений (предопределенные и определяемые);
возникновение исключений (самопроизвольное и управляемое);
распространение исключений (статика или динамика);
реакция на исключения (пластырь или катапульта - см. ниже).
Кроме этого, уделим внимание другим особенностям исключений В
частности, особенностям исключений в асинхронных процессах).
8.3.1. Определение исключений
Рассмотрим концепцию исключения, ориентируясь на Аду, стараясь больше
уделять внимания "авторской позиции", т.е. объяснять, почему при
проектировании ЯП были приняты излагаемые решения. Основой послужат,
конечно, принципы полноты, минимальных возмущений и минимального ущерба.
Все потенциальные исключения в программе на Аде имеют индивидуальные
имена и известны статически. Они либо предопределены, либо объявлены
(определены) программистом.
Предопределенные исключения касаются, естественно, самых общих
ситуаций. Например, при нарушении ограничений, связанных с типом
(ограничений допустимого диапазона значений, диапазона индексов и т.п.)
возникает (иногда говорят "возбуждается") исключение нарушение_ограничения
(constraint_error); при ошибках в числовых расчетах (переполнение, деление
на нуль, исчезновение и т.п.) - исключение численная_ошибка (numeric_error);
при неправильной компоновке программы (отсутствие тела нужного программного
сегмента и т.п.) - исключение нет_сегмента (program_error); при нехватке
памяти для размещения динамических объектов - исключение нет_памяти; при
нарушении во взаимодействии асинхронных процессов (аварийное или нормальное
завершение процесса, содержащего вызываемый вход и т.п.) - исключение
ошибка_взаимодействия (tasking_error).
Если, например, объявить
A:array (1 .. 10) of INTEGER ;
то при I = 11 или I = 0 в момент вычисления выражения A(I) возникает
предопределенное исключение нарушение_ограничения.
На уровне ЯП принцип полноты исключений обеспечен тем, что нарушение
любого языкового требования при выполнении программы на Аде приводит к
возникновению некоторого предопределенного исключения.
Принцип минимальных возмущений проявляется в том, что предопределенные
исключения возникают без какого бы то ни было указания программиста. Так что
он, во-первых, избавлен от необходимости проектировать их возникновение, во-
вторых, упомянутые указания не загромождают программу и, в-третьих,
соответствующие предопределенные проверки могут быть в принципе реализованы
аппаратурой или авторами компилятора так, чтобы требовать минимума ресурсов
при исполнении программы. Например, во фрагменте программы
<<B>>
declare
A: float;
begin
...
A:=X*X;
Y:=A*EXP(A); -- возможно переполнение при возведении в степень
-- или при умножении
exception -- ловушка исключений
when NUMERIC_ERROR ==> Y:=FLOAT'LAST; -- наибольшее вещественное
PUT ('Переполнение при вычислении Y в блоке B');
end B ;
четко отделена часть, реализующая основную функцию фрагмента (до ключевого
слова exception, начинающего ловушку исключений), и часть, реализующая
предусмотренную программистом реакцию на предопределенное исключение
численная_ошибка, возникающее при переполнении - это ловушка исключений.
Первую часть программист мог писать, не думая об исключениях вообще, а
затем мог добавить ловушку. Работать эта ловушка будет тогда и только тогда,
когда возникнет исключение, а затрат на проверку переполнения в программе
нет вовсе - обычно это дело аппаратуры. После реакции на исключение
переменная У получит "правдоподобное" значение, позволяющее сохранить
работоспососбность программы после аварии, а программист получит точное
сообщение об аварии в своих собственных обозначениях.
Упражнение. Попытайтесь написать эквивалентную программу, не пользуясь
аппаратом исключений. Убедитесь, что при этом приходится нарушать все три
принципа из 8.2.
Принцип минимальных повреждений (минимального ущерба) в современных ЯП
почти не влияет на возникновение исключений (хотя мог бы и влиять, позволив
управлять информацией, которая становится доступной при возникновении
исключения). Зато он существенно влияет на их определение и обработку. В
частности, именно для того, чтобы программист мог предусмотреть быструю и
точную реакцию на конкретное возникновение исключения, для одного и того же
исключения можно объявить несколько различных реакций, учитывающих
соответствующий контекст.
Определяемые исключения явно вводятся программистом посредством
объявления исключения. Например, объявление
объект_пуст, ошибка_в_данных : exception ;
вводит два исключения (exception). Очень похоже на объявление двух объектов
предопределенного типа exception. Такое объявление можно считать
спецификацией исключения (хотя оно так не называется). Программист обязан
задать также хотя бы одну реакцию на введенное исключение (примеры чуть
ниже). Совокупность таких реакций играет роль "тела" ("реализации")
объявленного программистом исключения.
Таким образом, для исключений также действует принцип разделения
спецификации и реализации. Ловушку (в которой размещаются реакции на
исключения) также естественно считать объявлением.
Вопрос. Что естественно считать "использованием" исключения?
Определяемые исключения возникают в момент, явно указываемый в
программе посредством оператора исключения (raise). Например, результатом
исполнения оператора
raise ошибка_в_данных;
служит возникновение исключения ошибка_в_данных.
Факт возникновения исключения переводит исполнителя в новый режим,
режим обработки исключения - происходит так называемое "распространение"
исключения (ищется подходящая "ловушка исключений"), а затем выполняется
"реакция на исключение", описанная в найденной ловушке. В этом режиме в
основном и действуют упоминавшиеся "априорные правила поведения
исполнителя". Они определяют, как найти реакцию на исключение и что делать
после выполнения предписанных в ней действий. Как уже сказано, именно на
выбор этих правил влияет принцип минимальных повреждений. Рассмотрим эти
правила.
8.3.2. Распространение исключений. Принцип динамической ловушки
Итак, с каждым исключением может быть связана серия ловушек, содержащих
реакции на исключение и расположенных в различных программных конструктах. С
учетом принципа минимальных повреждений в современных ЯП принят принцип
динамического выбора ловушки - всегда выбирается реакция на возникшее
исключение из ловушки, динамически ближайшей к месту "происшествия", т.е. в
режиме распространения исключения существенно используется динамическая
структура программы.
Другими словами, конкретные действия исполнителя зависят от
динамической цепочки вызовов, ведущей к тому программному конструкту, в
котором возникло исключение. Поэтому в соответствующей реакции появляется
возможность учесть динамический, а не только статический контекст
чрезвычайной ситуации. Это, конечно, помогает предотвратить распространение
повреждений.
Поясним принцип динамической ловушки на примере фрагмента программы
procedure P is
ошибка : exception ;
procedure R is
begin
. . . - - (1)
end R ;
procedure Q
begin
R ; -- вызов процедуры R ;
. . . -- (2)
exception -- первая ловушка исключений
. . .
when ошибка => PUT("ОШИБКА в Q") ; -- реакция на исключение
-- "ошибка" в первой ловушке
. . .
end Q ;
begin
. . . -- (3)
Q ; -- вызов процедуры Q
. . .
exception -- вторая ловушка
. . .
when ошибка => PUT("ОШИБКА в P") ; -- другая реакция на
-- то же исключение во второй ловушке
end P ;
Если исключение "ошибка" возникает на месте (3), то сработает реакция
на это исключение во второй ловушке и будет напечатано "ошибка в Р". Если то
же исключение возникнет на месте (2), т.е. при вызове процедуры Q и не в R,
то сработает реакция в первой ловушке и будет напечатано "ошибка в Q".
Пока подбор реакции как по динамической цепочке вызовов, так и по
статической вложенности конструктов дал одинаковые результаты.
А вот когда исключение "ошибка" возникает на месте (1) в теле
процедуры Q (при вызове процедуры R, в которой ловушки нет), то отличие
динамического выбора от статического проявляется наглядно. Статический
выбрал бы реакцию из второй ловушки в теле P, а динамический выберет реакцию
из первой ловушки в теле Q.
Будет напечатано "ошибка в Q", что существенно точнее отражает суть
случившегося. Именно для того, чтобы можно было точнее, конкретнее