Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Языки программирования. Концепции и принципы
.pdf
Исключения
Обратите внимание, объявляются исключения статически, подобно перемен
ным и процедурам, а реакции на них выбираются динамически из статически оп
ределенного множества возможных реакций.
Если разрешить вводить новые имена исключений динамически, то следовало бы
создавать динамически и реакции на них, то есть динамически создавать програм
му. Такого рода возможности противоречат концепции статического контроля и
в современных языках индустриального программирования практически не встре
чаются.
Вообще, в современных ЯП поведение исполнителя в режиме обработки исключе
ний довольно жестко регламентировано. Нет прямых аналогов, например такой
житейской возможности, как сообщить «начальству» и ждать распоряжений «на
месте происшествия». Или позвонить сразу и в милицию, и в скорую помощь,
и в пожарную охрану, одновременно принимая собственные меры. Конечно, всякое
поведение можно моделировать, но, например, несколько исключений в одном ме
сте возникнуть не могут.
191
8.3.3. Реакция на исключение –
принципы пластыря и катапульты
Принятая в ЯП стратегия обработки исключений прямо связана со взглядом на
сущность исключений. Этот взгляд, в свою очередь, зависит от важнейших требо
ваний, определивших авторскую позицию при создании ЯП. Хотя в итоге разли
чия поведения могут показаться не такими уж значительными, рассмотреть их
обоснование и интересно, и поучительно. Выберем для определенности два ЯП:
ПЛ/1 и Аду. Скажем заранее, что различия касаются лишь продолжения работы
после реакции на исключение.
Принцип пластыря. Сначала о ПЛ/1. Несколько упрощая, можно сказать, что
один из основных принципов конструирования языка ПЛ/1 – «предпочитать такие
истолкования конструкций, которые позволяют оправдать их дальнейшее исполне
ние». В соответствии с этим принципом введены многочисленные правила подразу
меваемых преобразований данных, допустимы различного рода сокращения и т. п.
В согласии с этим принципом исключение трактуется как относительно ред
кое, но в целом естественное для выполняемого конструкта событие. При его об
работке следует направить усилия на скорейшее возобновление прерванного про
цесса. Эти усилия можно наглядно представить себе как наложение пластыря на
«рану». Естественная модель поведения – прервать исполняемый процесс, выз
вать «врачебную бригаду», после окончания «лечения» продолжить прерванный
процесс. Обратите внимание на три составляющих поведения – прервать, вызвать
(это значит, понять, кого вызвать, – врачебная бригада подбирается динамиче
ски) и продолжить.
Подобный взгляд полезен, например, при нехватке памяти – нужно вызвать
подпрограмму сборки мусора или подпрограмму динамического выделения памя
ти, а затем попытаться продолжить работу. Описанное отношение к сущности ис
ключения можно назвать принципом пластыря.

192
Однако где гарантии, что «заклеенный» процесс сможет нормально работать?
Если исключение связано с окончанием файла или нарушением диапазона, то бес
смысленно продолжать работу прерванного процесса. В ПЛ/1 в таких случаях в
реакции на исключение (после «лечения», если оно требуется) применяют пере
дачу управления туда, откуда признано разумным продолжать работу. Например:
ON ENDFILE GOTO Ml;
По многим причинам это далеко не лучшее решение. Одна из основных при
чин – в том, что динамическая структура программы оказывается слабо связан
ной с ее статической структурой. Чтобы разобраться в программе, приходится
«прокручивать» каждый оператор. Короче говоря, решение с передачей управле
ния в общем случае затрудняет чтение и отладку программ. По «неструктуриро
ванности» это решение можно сравнить с выходом из подпрограммы не по воз
врату, а по передаче управления. Что при этом происходит с динамической
цепочкой вызовов? Остается только гадать или определять, руководствуясь «тон
кими» правилами!
Итак, будем считать обоснованным, что решение с передачей управления проти
воречит концепции «структурного программирования». В Аде стараются обойтись
без goto, тем более что таким способом «далеко не уйдешь», – в этом ЯП самая вло
женная последовательность операторов, окружающая объявление метки, должна
окружать и оператор перехода на эту метку. А без передачи управления принцип
пластыря не позволяет адекватно обрабатывать многие виды исключений.
Упражнение. Приведите соответствующие примеры.
Современное состояние языков программирования
Принцип катапульты. Одно из ключевых требований к языку Ада – способ
ствовать надежному программированию. Другими словами, следует стремиться
к минимуму отказов изза ошибок в программе и в данных. Когда же отказ неизбе
жен, то следует обеспечить по меньшей мере осмысленную диагностику.
Требование надежности оправдывает трактовку исключения как свидетель
ства полной непригодности «аварийного процесса» (процесса, где возникло ис
ключение) к нормальной работе в создавшихся условиях. Стремясь к минимуму
отказов, следует не «лечить» аварийный процесс, а нацелить обработку исключе
ния на локализацию последствий «аварии», на создание возможности продол
жать работу тех (связанных с аварийным) процессов, которых авария пока не кос
нулась.
Саму обработку исключения в «аварийном» процессе обычно разумно рас
сматривать скорее не как «лечение», а как «посмертную выдачу» – попытку со
хранить как можно больше сведений для анализа ситуации на уровне иерархии,
принимающем решения о последующих действиях.
Именно такой принцип действует в Аде (ведь надежность – одна из основных
целей этого ЯП), а также в ЯП Эль76 для машин серии Эльбрус.
При такой цели естественная стратегия – последовательно признавать аварий
ными вложенные процессы (начиная с самого внутреннего) до тех пор, пока среди
них не найдется процесс, в котором приготовлена реакция на возникшее исключе
ние. При этом аварийные процессы, в которых нет нужной реакции, последова

Исключения
тельно завершаются аварийным способом («катапультированием»). Найденная
в конечном итоге реакция на исключение призвана обеспечить нормальное продол
жение работы уцелевших процессов (и, возможно, выдачу сообщения об ошибке).
Итак, никакого возврата к аварийному процессу при такой стратегии нет,
а значит, нет и опасности вызвать «лавину» исключений и сообщений об авариях.
Если ведущий процесс сочтет возможным, он может снова запустить (в новых
условиях) и бывший аварийный процесс. Но решение об этом не встроено в се
мантику ЯП, а программируется на уровне иерархии, высшем по отношению
к аварийному процессу.
Назовем описанный принцип обработки исключений принципом катапульты.
Название связано с тем, что исключение заставляет управление немедленно поки
нуть признанный аварийным процесс, приняв меры к спасению самой ценной ин
формации (вполне аналогично тому, как катапультируется с аварийного самолета
летчик, спасая самое ценное – человеческую жизнь).
Именно этот принцип поведения исполнителя в исключительных ситуациях
воплощен в Аде (как целиком отвечающий требованиям к этому ЯП и, в частно
сти, способствующий надежному и структурному программированию). Пример
на стр. 188 показывает, как можно ликвидировать аварию и продолжить работу
(после реакции на исключение управление покидает блок В).
193
8.3.4. Ловушка исключений
Итак, в зависимости от ЯП реакция на исключение может быть и «пластырем», и
«катапультой». Осталось объяснить, как она устроена.
В общем случае тела подпрограмм, тела пакетов, тела задач, а также блоки со
держат в конце обычной последовательности операторов еще часть, отделенную
ключевым словом exception. Это и есть ловушка исключений. Она устроена ана
логично оператору выбора, но вместо значений перечисляемого типа после клю
чевого слова when фигурируют имена исключений.
Например:
begin
... -- последовательность операторов
exception -- ловушка исключений
when плохо_обусловленная | численная ошибка =>
PUT("матрица плохо обусловлена");
when others =>
PUT("фатальная ошибка");
raise ошибка;
end;
Альтернатива others, как обычно, выбирается в том случае, когда не выбраны
остальные. В нашем примере при возникновении исключения плохо_обусловлен
ная или численная_ошибка (первое – объявляемое, второе – предопределенное)
печатается «плохо обусловленная матрица» и обработка исключения завершается
(затем продолжает нормально работать динамически объемлющий процесс). Лю

194
бые другие исключения будут пойманы альтернативой others, будет напечатано
«фатальная ошибка», и возникнет новое исключение «ошибка» как результат ра
боты оператора исключения (raise).
Это исключение будет распространяться в динамически объемлющих процес
сах, пока не попадет в ловушку (для предопределенных исключений ловушки
предусмотрены в предопределенном пакете «система»). Если бы второй альтер
нативы не было, то любое исключение, отличное от двух указанных в первой аль
тернативе нашей ловушки, распространялось бы по динамически объемлющим
процессам до «своей» ловушки.
Современное состояние языков программирования
8.4. Дополнительные особенности
обработки исключений
Исключения в объявлениях. Когда исключение возникает в объявлениях некото
рого конструкта, то оно немедленно распространяется на динамически объемлю
щие конструкты. Собственная ловушка конструкта рассчитана только на исклю
чения, возникшие среди операторов конструкта. Это правильно, так как в
ловушке могут использоваться объекты, которые изза «недообработки» объявле
ний (ведь в них возникла авария) окажутся еще не существующими.
Приведенный выше пример показывает, что есть и еще одна причина – можно
попасть в бесконечный цикл, если при возникновении исключения «ошибка» ис
кать реакцию в той же ловушке. Напомним, что саму ловушку естественно счи
тать объявлением, а именно объявлением «тел» исключений.
Исключения в асинхронных процессах. Интересно рассмотреть особенности
распространения исключений на взаимодействующие задачи. У тела задачи нет
динамически объемлющего процесса – соответствующий телу задачи процесс ра
ботает асинхронно, с ним могут быть динамически связаны посредством рандеву
много других «равноправных» процессовпартнеров. С другой стороны, каждый
асинхронный процесс запускается при обработке некоторого фиксированного
объявления в определенном «родительском» конструкте.
Поэтому когда исключение возникает среди объявлений задачи, то эта задача
аварийно завершается и на месте запустившего ее объявления (задачи) в роди
тельском процессе возникает предопределенное исключение ошибка_взаимодей
ствия. Другими словами, о возникшей чрезвычайной ситуации в задаче немедлен
но «узнает» запускающий ее процесс (например, для того, чтобы можно было
заменить незапущенную задачу). Заменить можно потому, что запускаемая зада
ча еще наверняка не успела вступить во взаимодействие со своими асинхронными
партнерами (почему?) и их можно пока не предупреждать об аварии. Не успела
она и запустить дочерние процессыпотомки (потому что они считаются запущен
ными только после нормального завершения обработки раздела объявлений в ро
дительском процессе).
С другой стороны, если потенциальный клиент так и не запущенной (аварий
ной) задачи попытается обратиться к ее входу, то в этом клиенте на месте вызова

Исключения
195
входа возникнет исключение ошибка_взаимодействия (такое исключение всегда
возникает, если мастер, к которому пытаются обратиться, оказывается неработа
ющим). Так что исключение в случае аварии в одном из асинхронных процессов
распространяется не только «линейно» вверх по цепочке от потомков родителям,
но и «веером» ко всем клиентам.
Когда же исключение возникает не в объявлениях, а в теле задачи и в своем
распространении доходит до самой внешней в этом теле ловушки, то задача завер
шается аварийно (независимо от того, «поймано» ли исключение), но в запустив
шем ее процессеродителе исключение не возникает. Повидимому, потому, что в
общем случае ничего разумного он сделать не сможет – перезапускать аварийную
задачу считается опасным: она уже успела поработать с партнерами, причем не
только с клиентами, но и мастерами, а также запустить своих потомков.
Упражнение. Предложите версии ответа на вопрос, почему не возникает исклю
чение в мастерах, с которыми могла взаимодействовать аварийная задача, или в ее
потомках.
Подсказка. Возможно, авария не имеет к ним никакого отношения.
Заметим, что исключения, возникающие при рандеву, считаются возникшими
в обоих партнерах и распространяются в них независимо и асинхронно.
Уточнение к принципу динамической ловушки. Поиск ловушки происходит
с учетом динамической цепочки вызовов любых блоков, не обязательно процедур,
пока она есть. Выше – с учетом статической вложенности объявлений, затем, воз
можно, снова динамической цепочки вызовов и т. д.
Универсальная ловушка. Описанные ловушки требуют знать и учитывать все
имена потенциальных исключений – это не всегда удобно и даже возможно.
Иногда нужно единообразно реагировать на любое исключение, то есть иметь
средство абстракции от характера исключения, средство реагировать на сам факт
его возникновения (на сам факт аварии – не важно, какой именно). Обычно это,
по сути, потребность принять лишь меры общего характера, но отложить приня
тие конкретных решений до достижения «достаточно компетентных» уровней
иерархии.
Такая потребность возникает, вопервых, на уровнях программной иерархии,
которые просто некомпетентны содержательно реагировать на содержательные
аварии, и, вовторых, в таких структурах, где возникшее исключение статически
невидимо (и потому они формально не могут содержать ловушку с именем такого
исключения).
Упражнение. Придумайте пример такой структуры.
Пример типичной программной иерархии:
package обслуживание is
нет_исполнителей, нет_ресурсов, нет_заказов : exception; -- содержательные
-- исключения
procedure распределить_работу is
...

196
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(‘Bac много, а я одна!);
when нет_заказов =>
PUT(‘Íåò заказов, нужна реклама!);
end обслуживание;
Ловушка с альтернативой «when others» – это и есть ловушка, пригодная для
любых исключений. Оператор raise перевозбуждает последнее возбужденное ис
ключение, которое продолжает распространяться обычным методом. Все проис
ходит почти так же, как если бы ловушки вообще не было. Но это «почти» – пред
варительная реакция соответствующих уровней программной иерархии.

Исключения
197
Выход за границы видимости. Исключения нет_ресурсов (и др.) можно
объявить и на нижних уровнях иерархии, но обрабатывать (без учета их особенно
стей) на верхних уровнях в универсальных ловушках. Здесь в Аде нет статическо
го контроля. Повидимому, это считается более надежным, чем провоцировать от
сутствие исключений изза опасений, что не удастся придумать адекватную
реакцию (а при отсутствии таковой пришлось бы вставлять чисто формально ре
акцию raise;). В других ЯП (с параметрамипроцедурами) подобные случаи были
бы вообще статически неконтролируемыми (
package Ð is
procedure f;
procedure Pr(f : proc) is -- параметр-процедура
f; -- здесь e не видно, но может распространяться
end Pr; -- нет оснований контролировать наличие ловушки
-- исключения е – ведь возможны различные параметры-процедуры
end Р;
package body Р is
å : exception;
procedure f is
raise e;
end f; -- ловушки нет
end P; -- ловушки также нет
with P; use P;
begin
Pr(f); -- здесь e видно
end;
exception
when t => S; -- ловушки для e не оказалось
end;
почему?). Например:
Можно и так запрограммировать Рr:
procedure Pr(f; proc) is
begin
f;
exception -- универсальная ловушка;
-- e невидимо, но обрабатывается
when others =-> ÷òî-òî; raise;
-- и распространяется дальше
end Ðr;
Различия между исключениями в объявлениях и операторах. Суть разли
чий состоит в том, что ловушка блока не обслуживает исключений, возникших
при обработке объявлений этого блока. Ловушка только для операторов.
Например:
<< Â >>
declare
а : integer := f(22); – при вычислении f возможны исключения
х : real;
begin

198
...
exception
when числ_ош => PUT(‘ОШ в блоке В’);
PUT(a); PUT(x);
end Â;
Современное состояние языков программирования
Исключение, возникшее при вычислении f, немедленно распространяется
в точку, указанную стрелкой. (Почему так?) Ведь если исключение возникло сре
ди объявлений, то нет гарантий, что нормально введены объекты, используемые
в ловушке, а задача ловушки – сохранить объекты (для анализа) присваиванием
глобальным переменным или выводом. Если в такой ситуации не обойти ло
вушку, то ее исполнение может нанести глобальным объектам дополнительный
ущерб (а также привести к новым исключениям).
Напомним, что при этом сама ловушка тоже считается объявлением, а именно
«телом исключения».
Можно считать, что здесь проявляется еще один полезный принцип – принцип
минимизации каскадов (взаимозависимых) исключений. С другой стороны, его
можно непосредственно вывести из принципа минимальных повреждений.
Упражнение. Объясните смысл и обоснуйте принцип минимальных каскадов.
Подавление исключений (оптимизирующие указания). Для полноты пред
ставления об обработке исключений осталось добавить, что в случае, когда неко
торые проверки ограничений, потенциально приводящие к возникновению ис
ключений, считаются дорогими (неприемлемо ресурсоемкими), их можно
отменить с помощью так называемых прагм (оптимизирующих указаний) вида
pragma подавить(проверка_индексов, на => таблица);
Если реализация способна реагировать на такие указания (они не обязательны
для исполнения), то в нашем случае проверка индексов будет отменена для всех
объектов типа «таблица». Можно управлять исключениями и с точностью до отдель
ных объектов. Конечно, подобными указаниями следует пользоваться очень осто
рожно. В Аде есть и другие оптимизирующие указания. Все они не обязательны.
Выводы. Итак, на примере аппарата исключений в Аде показаны в действии
все три основных принципа стр. 185. В частности, этот аппарат позволяет отде
лить программирование содержательной функции программного сегмента от
программирования его взаимодействия с другими сегментами в чрезвычайных
(аварийных) обстоятельствах.
Программируя содержательную функцию, можно абстрагироваться от не
обычных ситуаций, а программируя взаимодействие в необычных ситуациях,
в значительной степени абстрагироваться от содержательной функции сегмента,
опираясь на априорные правила поведения Адаисполнителя. Для особо важных
чрезвычайных ситуаций можно заранее заготовить названия и ловушки в библио
течных модулях, а также программировать ловушки в пользовательских модулях,
конкретизируя необходимую реакцию. Таким образом, концепция исключения –
одна из компонент общего аппарата абстракцииконкретизации в ЯП. Ее можно

Исключения
было бы довести и до уровня модульности, физически отделив ловушки от тел
сегментов. Повидимому, такая возможность появится в ЯП будущего. (Точнее,
уже появилась в языке SDL/PLUS [15] и в последних версиях языка Модула2).
Стоит заметить, что исключения – весьма специфический аспект ЯП, очевид
ным образом нуждающийся в развитии и определении более четкого места
в структуре ЯП в целом. Ведь исключения – это не сообщения, но похожи; не пре
рывания, но похожи; не goto, но похожи; не процедуры, но похожи; не типы, но
похожи.
Действительно, это не обычные сообщения, потому что нет явного отправите
ля и адресата, нет явной структуры сообщения, но вместе с тем функция исключе
ния – сообщить одному или нескольким процессам (содержащим подходящие
ловушки) о чрезвычайных обстоятельствах. Это не обычные прерывания, потому
что не предполагают обязательного участия «средств низкого уровня» – аппарату
ры или ОС для своей обработки. Хотя исключения было бы правильно назвать
«прерываниями на уровне исходного ЯП». Это не обычные процедуры, хотя ис
ключениям соответствуют последовательности действий, вызываемых по именам
(но тела связываются с именами не статически, а динамически, недопустимы па
раметры, необязателен вызов и т. д.). Это не обычный тип, хотя можно объявлять
соответствующие «объекты», потому что нельзя передавать их какимлибо опре
деляемым пользователем операциям (нельзя передавать как параметры).
И вся эта увлекательная специфика исключений объясняется их особой ролью –
обслуживанием потребности в разделении нормального и «чрезвычайного» ре
жимов работы программы с учетом принципов минимальных возмущений и ми
нимальных повреждений.
Упражнение. Дополните анализ аппарата исключений в Аде с точки зрения связей
с другими языковыми конструктами. Проделайте то же для других известных вам
ЯП (например, Эль76 или ПЛ/1).
199

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
