В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 36
Текст из файла (страница 36)
задача завершается аварийно (не зависимо от того, "поймано" ли исключение),
но в запустившем ее процессе-родителе исключение не возникает. По-видимому,
потому, что в общем случае ничего разумного он сделать не сможет -
перезапускать аварийную задачу считается опасным: она уже успела поработать
с партнерами, причем не только с клиентами, но и мастерами, а также
запустить своих потомков.
Упражнение. Предложите версии ответа на вопрос, почему не возникает
исключение в мастерах, с которыми могла взаимодействовать аварийная задача,
или в ее потомках.
Подсказка. Возможно, авария не имеет к ним никакого отношения.
Заметим, что исключения, возникающие при рандеву, считаются возникшими
в обоих партнерах и распространяются в них независимо и асинхронно.
Уточнение к принципу динамической ловушки. Поиск ловушки происходит с
учетом динамической цепочки вызовов любых блоков, не обязательно процедур,
пока она есть. Выше - с учетом статической вложенности объявлений, затем,
возможно, снова динамической цепочки вызовов и т.д.
Универсальная ловушка. Описанные ловушки требуют знать и учитывать все
имена потенциальных исключений - это не всегда удобно и даже возможно.
Иногда нужно единообразно реагировать на любое исключение - т.е. иметь
средство абстракции от характера исключения, средство реагировать на сам
факт его возникновения (на сам факт аварии - не важно какой именно). Обычно
это по сути потребность принять лишь меры общего характера, но отложить
принятие конкретных решений до достижения "достаточно компетентных" уровней
иерархии.
Такая потребность возникает, во-первых, на уровнях программной
иерархии, которые просто не компетентны содержательно реагировать на
содержательные аварии, и, во-вторых, в таких структурах, где возникшее
исключение статически невидимо (и потому они формально не могут содержать
ловушку с именем такого исключения).
Упражнение. Придумайте пример такой структуры.
Пример типичной программной иерархии:
package обслуживание is
нет_исполнителей, нет_ресурсов, нет_заказов : exсeption;
-- содержательные исключения
procedure распределить_работу is
. . .
raise нет_исполнителей; -- содержательное исключение, но что
. . . -- делать при его возникновении здесь - неясно.
end распределить_работу; -- Поэтому ловушки нет. Никакой
-- реакции!
procedure проверить_исполнение is
. . .
raise нет_заказов; -- по той же причине ловушки нет
. . .
end проверить_исполнение;
procedure выполнить is
. . .
raise нет_ресурсов; -- по той же причине ловушки нет
. . .
end выполнить;
procedure обслужить_категорию_А (f : файл) is
. . . -- "некомпетентный" уровень
begin
открыть (f);
распределить_работу;
. . .
выполнить;
. . .
проверить_исполнение;
. . .
закрыть (f);
exception -- универсальная ловушка (для любых исключений)
when others => закрыть(f); raise; -- что бы ни произошло, нужно
end обслужить_ категорию_А; -- закрыть файл, а с остальным
-- пусть разбираются выше
. . .
exception -- это "компетентный" уровень
when нет_исполнителей => PUT(`Вас много, а я одна!);
when нет_заказов => PUT(`Нет заказов, нужна реклама!);
end обслуживание;
Ловушка с альтернативой "when others" - это и есть ловушка, пригодная
для любых исключений. Оператор raise перевозбуждает последнее возбужденное
исключение, которое продолжает распространяться обычным методом. Все
происходит почти так же, как если бы ловушки вообще не было. Но это "почти"
- предварительная реакция соответствующих уровней программной иерархии.
Выход за границы видимости. Исключения нет_ресурсов (и др.) можно
объявить и на нижних уровнях иерархии, но обрабатывать (без учета их
особенностей) на верхних уровнях в универсальных ловушках. Здесь в Аде нет
статического контроля. По-видимому, это считается более надежным, чем
провоцировать отсутствие исключений из-за опасений, что не удастся придумать
адекватную реакцию (а при отсутствии таковой пришлось бы вставлять чисто
формально реакцию raise;). В других ЯП (с параметрами-процедурами) подобные
случаи были бы вообще статически не контролируемыми (почему?). Например
package P is
procedure f;
procedure Pr (f : proc) is --параметр-процедура
f; -- здесь e не видно, но может распространяться
end Pr; -- нет оснований контролировать наличие ловушки
-- исключения e - ведь возможны различные параметры-процедуры
end P;
package body P is
e : exeption;
procedure f is
raise e;
end f; -- ловушки нет
end P; -- ловушки также нет
with P; use P;
begin
Pr(f); -- здесь e - видно
end;
exeption
when t => S; -- ловушки для e не оказалось
end;
Можно и так запрограммировать Pr:
procedure Pr (f : proc) is
begin
f;
exeption -- универсальная ловушка; e невидимо, но обрабатывается
when others => что-то; raise; -- и распространяется дальше
end Pr ;
Различия между исключениями в объявлениях и операторах. Суть различий
состоит в том, что ловушка блока не обслуживает исключений, возникших при
обработке объявлений этого блока. Ловушка только для операторов.
Например,
<< B >>
declare
a : integer := f(22); -- при вычислении f возможны исключения
x : real;
begin
. . .
exсeption
when числ_ош ==> PUT ('ОШ в блоке B');
PUT(a);
PUT(x);
end B;
---->
Исключение, возникшие при вычислении f, немедленно распространяется в
точку, указанную стрелкой. (Почему так?). Ведь если исключение возникло
среди объявлений, то нет гарантий, что нормально введены объекты,
используемые в ловушке, а ведь задача ловушки - сохранить объекты (для
анализа) присваиванием глобальным переменным или выводом. Если в такой
ситуации не обойти ловушку, то ее исполнение может нанести глобальным
объектам дополнительный ущерб (а также привести к новым исключениям).
Напомним, что при этом сама ловушка тоже считается объявлением - а
именно "телом исключения".
Можно считать, что здесь проявляется еще один полезный принцип -
принцип минимизации каскадов (взаимозависимых) исключений. С другой стороны,
его можно непосредственно вывести из принципа минимальных повреждений.
Упражнение. Объясните смысл и обоснуйте принцип минимальных каскадов.
Подавление исключений (оптимизирующие указания). Для полноты
представления об обработке исключений осталось добавить, что в случае, когда
некоторые проверки ограничений, потенциально приводяшие к возникновению
исключений, считаются дорогими (неприемлемо ресурсоемкими), их можно
отменить с помощью так называемых прагм (оптимизирующих указаний) вида
pragma подавить (проверка_индексов, на => таблица);
Если реализация способна реагировать на такие указания (они не
обязательны для исполнения), то в нашем случае проверка индексов будет
отменена для всех объектов типа "таблица"). Можно управлять исключениями и с
точностью до отдельных объектов. Конечно, подобными указаниями следует
пользоваться очень осторожно. В Аде есть и другие оптимизирующие указания.
Все они не обязательны.
Выводы. Итак, на примере аппарата исключений в Аде показаны в действии
все три основные принципа (п.8.2). В частности, этот аппарат позволяет
отделить программирование содержательной функции программного сегмента от
программирования его взаимодействия с другими сегментами в чрезвычайных
(аварийных) обстоятельствах.
Программируя содержательную функцию, можно абстрагироваться от
необычных ситуаций, а программируя взаимодействие в необычных ситуациях, в
значительной степени абстрагироваться от содержательной функции сегмента,
опираясь на априорные правила поведения Ада-исполнителя. Для особо важных
чрезвычайных ситуаций можно заранее заготовить названия и ловушки в
библиотечных модулях, а также программировать ловушки в пользовательских
модулях, конкретизируя необходимую реакцию. Таким образом, концепция
исключения - одна из компонент общего аппарата абстракции-конкретизации в
ЯП.
Ее можно было бы довести и до уровня модульности, физически отделив
ловушки от тел сегментов. По-видимому, такая возможность появится в ЯП
будущего. (Точнее, уже появилась в языке SDL/PLUS [15] и в последних версиях
Модулы-2).
Стоит заметить, что исключения - весьма специфический аспект ЯП,
очевидным образом нуждающийся в развитии и определении более четкого места в
структуре ЯП в целом. Ведь исключения - это не сообщения, но похожи; не
прерывания, но похожи; не goto, но похожи; не процедуры, но похожи; не
типы, но похожи.
Действительно, это не обычные сообщения, потому что нет явного
отправителя и адресата, нет явной структуры сообщения, но вместе с тем
функция исключения - сообщить одному или нескольким процессам (содержащим
подходящие ловушки) о чрезвычайных обстоятельствах. Это не обычные
прерывания, потому что не предполагают обязательного участия "средств
низкого уровня" - аппаратуры или ОС для своей обработки. Хотя исключения
было бы правильно назвать "прерываниями на уровне исходного ЯП". Это не
обычные процедуры, хотя исключениям соответствуют последовательности
действий, вызываемых по именам (но тела связываются с именами не статически,
а динамически, не допустимы параметры, не обязателен вызов и т.д.). Это не
обычный тип, хотя можно объявлять соответствующие "объекты", потому что
нельзя передавать использовать их в каких-либо определяемых пользователем
операциях (нельзя передавать как параметры).
И вся эта увлекательная специфика исключений объясняется их особой
ролью - обслуживанием потребности в разделении нормального и "чрезвычайного"
режимов работы программы с учетом принципов минимальных возмущений и
минимальных повреждений.
Упражнение. Дополните анализ аппарата исключений в Аде с точки зрения
связей с другими языковыми конструктами. Проделайте то же для других
известных Вам ЯП (например, Эль-76 или ПЛ/1).
9. Библиотека
9.1. Cтруктура библиотеки
До сих пор мы избегали подробного описания языковых свойств,
ограничиваясь сведениями, достаточными для демонстрации рассматриваемых
концепций и принципов. Однако в будущем мы намерены существенно затронуть
авторскую позицию, для которой, конечно, важны все тонкости ЯП (иначе они бы
в нем не появились). Более того, мы намерены изложить принципы, в
определенном смысле управляющие сложностью создаваемого ЯП. Для их понимания
необходимо, чтобы читатель был в состоянии в деталях сопоставить решения,
принятые авторами различных ЯП.
Поэтому в ближайших разделах, завершая знакомство с основными языковыми
абстракциями, мы подробно остановимся на избранных аспектах Ады, а именно на
раздельной компиляции, управлении видимостью идентификаторов и обмене с
внешней средой. Основная цель упоминания подробностей - продемонстрировать
сложность языка и возникающие в этой связи проблемы. Заинтересованного
читателя отсылаем к руководствам по Аде [16,17,18].
Ранее мы рассмотрели виды связывания раздельно транслируемых модулей в
Аде. Посмотрим на ту же самую проблему немного с другой стороны - обсудим
устройство программной (трансляционной) библиотеки. Ада - первый ЯП, в
котором особенности использования библиотеки тщательно проработаны и
зафиксированы в определении ЯП. В этом отношении полезно сравнить Аду,
например, с Фортраном.
9.2. Компилируемый (трансляционный) модуль
Компилятор получает "на вход" компилируемый модуль, который состоит из
(возможно пустой) спецификации контекста и собственно текста модуля.
Спецификация контекста содержит указатели контекста (with) и сокращений
(use). Займемся первым.
Уже было сказано, что with - средство явного указания односторонней
связи: в использующем модуле перечисляют имена необходимых ему библиотечных
модулей.
Таким образом, любое имя, используемое в данном модуле, должно быть
либо объявлено в самом этом модуле или в связанных с ним (при помощи
двусторонней связи!) библиотечных или родительских модулях; либо объявлено в
пакете STANDARD; либо предопределен; либо явно перечислено в указателе
контекста (with).