Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Языки программирования. Концепции и принципы

.pdf
Скачиваний:
2
Добавлен:
08.09.2026
Размер:
2 Мб
Скачать
☆
Нотация
Интересно отметить, что «возвращение пробела» как значащего символа свя
зано с пониманием «ключевых слов» просто как зарезервированных слов (а не «иероглифов», как в Алголе), ничем другим от остальных словлексем не отлича ющихся. Но тогда естественно запретить сокращать ключевые слова (иначе их можно спутать теперь уже не только с другими ключевыми словами, но и с идентификаторами). Это в целом полезное ограничение, так как способствует надежности программирования, помогая чтению за счет некоторой дисциплины письма (что вполне в духе индустриального программирования). Кстати, не оче видно, что напечатать слово procedure труднее, чем «рrос», с учетом переключе ния внимания на спецзнаки. К тому же современные системы подготовки текстов позволяют легко вводить словари сокращений (так что и чтения не затрудняют, и печатать удобно).
181
7.9. Лексемы в Аде
Лексемы в Аде аналогичны словам естественного языка. Они делятся на шесть классов: ограничители (знаки препинания), идентификаторы (среди которых – зарезервированные ключевые слова), числа, обозначения символов, строки и при мечания. В некоторых случаях, когда невозможно иначе однозначно выделить лексему, требуется явный разделитель между смежными лексемами. В качестве разделителя выступает или пробел, или управляющий символ, или конец строч ки. Пробел, естественно, не действует как разделитель в строках, примечаниях и в обозначении пробела (‘ ‘). Управляющие символы (кроме, возможно, горизон тальной табуляции, эквивалентной нескольким пробелам) всегда служат разде лителями лексем. Между лексемами (а также до первой и после последней лексе мы) текста допустимы несколько разделителей. Заметим, что каждая лексема должна располагаться на одной строке (ведь конец строки – разделитель).
Со списком ключевых слов Ады мы познакомились по ходу изложения. Мно
гие из них стали фактически стандартными для многих ЯП (procedure, begin, do и т. д.). Сокращать ключевые слова недопустимо.
Ниже следует описание классов лексем. Ограничитель. Это одиночный символ
&’()*+,–./:;<=>
и пара символов
=> .. ** := /= >= <= << >> < >
При этом символ может играть роль ограничителя только тогда, когда он не
входит в более длинную лексему (парный ограничитель, примечание, строку).
Идентификатор. Отличается от алгольного или паскалевского идентификато
ра только тем, что внутри него допускается одиночное подчеркивание. Пропис ные и строчные буквы считаются эквивалентными. Идентификаторы считаются различными, если отличаются хотя бы одним символом (в том числе и подчерки ванием), например 'А', ‘*’, '", ' ' и т. п.
182
Современное состояние языков программирования
Строка. Это последовательность графических символов, взятая в двойные ка вычки. Внутри строки двойная кавычка изображается повторением двойной ка вычки («»), например «Message of the day».
Примечание. Начинается двумя минусами и завершается концом строки.
Число. Примеры целых чисел:
65_536 , 10.000 2#1111_1111# , 16#FF# , 016#0FF#
-- целые константы, равные 255
16#Å#Å1 , 2#1110_0000#
-- ýòî 222
Примеры вещественных чисел:
16#F.FF#E+2 , 2#1.1111_1111_111#Å11
-- 4095.0 (Пробелы внутри не допускаются – ведь они разделители.)
Исключения
8.1. Основная абстракция.............. 184
8.2. Определяющие требования .... 185
8.3. Аппарат исключений в ЯП ....... 187
8.4. Дополнительные особенности
обработки исключений .................. 194
Глава 8
184
Современное состояние языков программирования
8.1. Основная абстракция
Представим себе заводского технолога, планирующего последовательность опера ций по изготовлению, например, блока цилиндров двигателя внутреннего сгора ния. Аналогия с программированием очевидна. Соответствующая технологическая карта (программа) предусматривает отливку заготовки, фрезеровку поверхно стей и расточку отверстий. Каждый из этапов довольно подробно расписывается в технологической карте. Однако все предусматриваемые технологом подробно сти касаются создания именно блока цилиндров. В технологической карте, конеч но, не сказано, что должен делать фрезеровщик, если выйдет из строя фреза, если возникнет пожар, землетрясение, нападет противник. Если бы технолог был вы нужден планировать поведение исполнителя в любых ситуациях, то он никогда не закончил бы работу над такими «технологическими картами».
В сущности, специализация в человеческой деятельности основана на способ ности выделить небольшой класс ситуаций, считающихся существенными для этого вида деятельности, а от всех остальных абстрагироваться, считать чрезвы чайными, необычными, исключительными, требующими переключения в другой режим, в другую сферу деятельности.
Примерам нет числа. Кулинарный рецепт не описывает поведения хозяйки, если в процессе приготовления блюда зазвонит телефон; физические модели при менимы при определенных ограничениях и ничего не говорят о том, что будет при нарушении этих ограничений (например, из законов Ньютона нельзя узнать о по ведении объектов при релятивистских скоростях).
Вместе с тем важно понимать, что теми аспектами планируемой деятельности, от которых приходится абстрагироваться, ни в коем случае нельзя пренебрегать. При реальном возникновении чрезвычайных обстоятельств именно они и стано вятся определяющими. Так что в жизнеспособной системе, а тем более системе, претендующей на повышенную надежность, совершенно необходим аппарат, обеспечивающий адекватную реакцию системы на чрезвычайные ситуации. К счастью, технолог обычно вправе рассчитывать на интеллект, жизненный опыт и общую квалификацию исполнителячеловека.
Программист, вынужденный создавать программу для автомата, по необходи мости попадает в положение заводского технолога, которого заставляют писать инструкции по гражданской обороне или поведению во время пожара. Ведь на дежная программа должна вести себя разумно в любых ситуациях. Как выйти из положения, нам уже нетрудно догадаться – снова абстракция (и затем конкретизация). Нужно иметь возможность, занимаясь содержательной функ цией программы, отвлекаться от проблемы чрезвычайных обстоятельств, а зани маясь чрезвычайными обстоятельствами, в значительной степени отвлекаться от содержательной функции программы. Вместе с тем на подходящем этапе про граммирования и исполнения программы нужно, конечно, иметь возможность учесть все тонкости конкретных обстоятельств.
Таким образом, мы приходим к одной из важнейших абстракций программи рования – абстракции от чрезвычайных обстоятельств, от особых (исключитель
Исключения
185
ных) ситуаций. Будем называть их для краткости просто «исключениями», а со ответствующую абстракцию – абстракцией от исключений.
Проще говоря, речь идет об аппарате, поддерживающем систематическое раз
деление нормальной и ненормальной работы, причем не только программы, но в некотором смысле и программиста.
8.2. Определяющие требования
Требования к аппарату исключений в ЯП легко выводятся из самых общих сооб ражений (и тем не менее нигде не сформулированы). Представим их в виде трех принципов, непосредственно следующих из назначения исключений в условиях систематического, надежного и эффективного программирования.
Принцип полноты исключений: на любое исключение должна (!) быть пре
дусмотрена вполне определенная реакция исполнителя.
Формулировка не претендует на строгость. Конечно, имеется в виду «любое»
исключение не из реального мира, а из «мира» исполнителя (трудно предусмот реть реакцию компьютера, например, на любую попытку его поломать). Но зато действительно имеется в виду любое исключение из этого «мира», что немедлен но приводит еще к одному важному понятию, которому до сих пор мы не имели случая уделить достойного внимания.
Дело в том, что предусматривать определенную реакцию на любое исключение
в каждой программе во всех отношениях неразумно. Читатель легко поймет, поче му. Поэтому наиболее общие правила такого реагирования на исключения долж ны быть предусмотрены априори, еще до начала программирования, то есть авто рами ЯП. Другими словами, существенная часть реакции на исключения должна предусматриваться «априорными правилами поведения исполнителя», а не соб ственно программой.
К сожалению, этот принцип в полной мере воплотить не удается по многим
причинам (в частности, изза невозможности абсолютно точно определить ЯП или безошибочно программировать). Однако он полезен в качестве ориентира для авторов ЯП и программистов.
Об априорных правилах поведения исполнителя
Планировать поведение исполнителя означает, в частности, согласовывать его мо
дель мира с моделью решаемой задачи. Чем лучше эти модели согласованы априо
ри, тем проще планировать поведение, проще достичь взаимопонимания. В сущ
ности, изученные нами абстракции включают в ЯП именно для того, чтобы
приблизить мир исполнителя к миру решаемых задач. Однако до сих пор у нас не
было случая обратить внимание на то, что «мир исполнителя» не следует ограни
чивать операциями и данными (возможно, весьма мощными и разнообразными).
Сколь мощной ни была бы операция, ее нужно явно указать в программе. Между
тем одна из самых общих технологических потребностей – потребность писать
надежные программы, только что привела нас к абстракции новой категории, к аб
стракции от конкретной программы или понятию об априорных правилах поведе
ния исполнителя (то есть правилах поведения, предопределенных в ЯП и не обяза
тельно связанных непосредственно с какимилибо указаниями в программе).
186
Именно априорные правила поведения, а не специфические данные и операции характеризуют современные развитые ЯП. С этой точки зрения удобно рассматри вать и особенности взаимодействия асинхронных процессов, и перебор с возвра том (backtracking), и вызов процедур по образцу, и поведение экспертных систем, и вообще программирование достаточно высокоинтеллектуальных исполнителей (в том числе программирование человеческой деятельности). Связь с этим аспектом мира исполнителя вступает в действие при исполнении лю бой программы, как только возникает исключительная ситуация. Вместе с тем в развитом ЯП всегда имеются и специальные средства конкретизации, позволяю щие корректировать априорные правила поведения с учетом потребностей конк ретной программы. Важно понимать, что без априорных правил поведения исполнителя не обойтись. Ведь если программист захочет все предусмотреть, он должен планировать провер ку соответствующих условий. Но исключительные ситуации могут возникнуть в процессе исполнения программы проверки! Так что чисто программным путем задачу не решить. Необходим определенный исходный (априорный) уровень под готовки исполнителя. В жизни примерами «общих правил» служат обязанность вызвать пожарную ко манду по телефону 01, милицию – по 02, скорую помощь – по 03, а также выпол нить инструкцию по оказанию неотложной помощи пострадавшим, мобилизаци онное предписание и т. п. Пример конкретизации – подробный план эвакуации людей из помещения при по жаре, план мобилизационных и эвакуационных мероприятий на случай войны, пе речень занятий дежурного при отсутствии телефонных звонков (ведь для него обычная работа – отвечать на телефонные звонки, а их отсутствие – исключение).
Современное состояние языков программирования
Принцип минимальных возмущений: затраты на учет чрезвычайных обстоя тельств должны быть по возможности минимальными (при гарантии сохранения жизнеспособности системы).
Этот же принцип в терминах ЯП: языковые средства должны быть такими, чтобы забота об исключениях в минимально возможной степени сказывалась на всех этапах жизненного цикла программных сегментов, реализующих основную содержательную функцию программы.
Другими словами, аппарат исключений в ЯП должен быть таким, чтобы про граммирование поведения в чрезвычайных обстоятельствах могло быть в макси мально возможной степени отделено от программирования основной функции (в частности, не мешало понимать содержательную функцию программы), наклад ные расходы на исполнение основной функции могли быть минимальными и т. п.
Принцип минимальных повреждений: ущерб при возникновении исключе ний должен быть минимальным. Речь идет уже не о минимизации затрат на саму способность реагировать на исключения, а об ущербе (иногда принципиально не устранимом), который может быть нанесен при реальном возникновении чрезвы чайных для программы обстоятельств.
Например, разумный аппарат исключений должен предусматривать возмож ность реагировать на исключение как можно раньше и как можно точнее, чтобы предотвратить дальнейшее разрушение программ и данных при аварийном их функционировании. Ясно, что этот принцип имеет смысл только тогда, когда ис
Исключения
ключения трактуются как аварии или реально оказываются таковыми (часто это как раз априорные исключения). Например, завершение файла при чтении часто удобно считать исключением с точки зрения «нормальной» обработки его записей, однако в этом случае не имеет смысла говорить об авариях или повреждениях.
Полезные примеры, помогающие лучше прочувствовать суть изложенных
принципов, содержатся в [14].
187
8.3. Аппарат исключений в ЯП
Концепция исключения в ЯП содержательно имеет много общего с концепцией аппаратного внутреннего прерывания, однако могут быть и существенные отли чия. Ближе всего к понятию прерывания трактовка исключений в языке ПЛ/1.
Выделим четыре аспекта аппарата исключений:
• определение исключений (предопределенные и определяемые);
• возникновение исключений (самопроизвольное и управляемое);
• распространение исключений (статика или динамика);
• реакция на исключения (пластырь или катапульта – см. ниже).
Кроме этого, уделим внимание другим особенностям исключений, в частности
особенностям исключений в асинхронных процессах.
8.3.1. Определение исключений
Рассмотрим концепцию исключения, ориентируясь на Аду, стараясь больше уде лять внимания «авторской позиции», то есть объяснять, почему при проектирова нии ЯП были приняты излагаемые решения. Основой послужат, конечно, прин ципы полноты, минимальных возмущений и минимального ущерба.
Все потенциальные исключения в программе на Аде имеют индивидуальные
имена и известны статически. Они либо предопределены, либо объявлены (опре делены) программистом.
Предопределенные исключения касаются, естественно, самых общих ситуа
ций. Например, при нарушении ограничений, связанных с типом (ограничений допустимого диапазона значений, диапазона индексов и т. п.), возникает (иногда говорят «возбуждается») исключение нарушение_ограничения (constraint_error); при ошибках в числовых расчетах (переполнение, деление на нуль, исчезновение и т. п.) – исключение численная_ошибка (numeric_error); при неправильной ком поновке программы (отсутствие тела нужного программного сегмента и т. п.) – исключение нет_сегмента (program_error); при нехватке памяти для размещения динамических объектов – исключение нет_памяти; при нарушении во взаимодей ствии асинхронных процессов (аварийное или нормальное завершение процесса, содержащего вызываемый вход, и т. п.) – исключение ошибка_взаимодействия (tasking_error).
Если, например, объявить
А:аггау(1 .. 10) of INTEGER;
188
Современное состояние языков программирования
то при I=11 или I = 0 в момент вычисления выражения А(I) возникает предопре деленное исключение нарушение_ограничения.
На уровне ЯП принцип полноты исключений обеспечен тем, что нарушение любого языкового требования при выполнении программы на Аде приводит к возникновению некоторого предопределенного исключения.
Принцип минимальных возмущений проявляется в том, что предопределен ные исключения возникают без какого бы то ни было указания программиста. Так что, вопервых, программист избавлен от необходимости проектировать их воз никновение, вовторых, упомянутые указания не загромождают программу и, втретьих, соответствующие предопределенные проверки могут быть в принципе реализованы аппаратурой или авторами компилятора так, чтобы требовать минимума ресурсов при исполнении программы. Например, во фрагменте про граммы
<<Â>> declare
A: float; begin
...
À:=Õ*Õ;
Y:=A*EXP(A); -- здесь возможно переполнение при возведении в степень или
-- умножении
... exception – ловушка исключений
when NUMERIC_ERROR => Y:=FLOAT’LAST; – наибольшее вещественное
PUT(‘Переполнение при вычислении Y в блоке В’); end В;
четко отделена часть, реализующая основную функцию фрагмента (до ключевого слова exception), от части, реализующей предусмотренную программистом реак цию на предопределенное исключение численная_ошибка, возникающее при пе реполнении, – это ловушка исключений.
Первую часть программист мог писать, не думая об исключениях вообще, а за тем мог добавить ловушку. Работать эта ловушка будет тогда и только тогда, ког да возникнет исключение, а затрат на проверку переполнения в программе нет вовсе – обычно это дело аппаратуры. После реакции на исключение переменная Y получит «правдоподобное» значение, позволяющее сохранить работоспособность программы после аварии, а программист получит точное сообщение об аварии в своих собственных обозначениях (размещенное в операторе PUT).
Упражнение. Попытайтесь написать эквивалентную программу, не пользуясь ап паратом исключений. Убедитесь, что при этом приходится нарушать все три прин ципа со стр. 185–186.
Принцип минимальных повреждений (минимального ущерба) в современных ЯП почти не влияет на возникновение исключений (хотя мог бы и влиять, позво лив управлять информацией, которая становится доступной при возникновении исключения). Зато он существенно влияет на их определение и обработку. В част
Исключения
ности, именно для того, чтобы программист мог предусмотреть быструю и точную реакцию на конкретное возникновение исключения, для одного и того же исклю чения можно объявить несколько различных реакций, учитывающих соответ ствующий контекст.
Определяемые исключения явно вводятся программистом посредством
объявления исключения. Например, объявление
объект_пуст, ошибка_в_данных : exception;
вводит два исключения (exception). Очень похоже на объявление двух объектов предопределенного типа exception. Такое объявление можно считать специфика цией исключения (хотя оно так не называется). Программист обязан задать также хотя бы одну реакцию на введенное исключение (примеры чуть ниже). Совокуп ность таких реакций играет роль «тела» («реализации») объявленного програм мистом исключения.
Таким образом, для исключений также действует принцип разделения специ
фикации и реализации. Ловушку (в которой размещаются реакции на исключе ния) также естественно считать объявлением.
Вопрос. Что естественно считать «использованием» исключения?
Определяемые исключения возникают в момент, явно указываемый в про
грамме посредством оператора исключения (raise). Например, результатом ис полнения оператора
raise ошибка_в_данных;
служит возникновение исключения ошибка_в_данных.
Факт возникновения исключения переводит исполнителя в новый режим, ре
жим обработки исключения – происходит так называемое «распространение» ис ключения (ищется подходящая «ловушка исключений»), а затем выполняется «реакция на исключение», описанная в найденной ловушке. В этом режиме в ос новном и действуют упоминавшиеся «априорные правила поведения исполните ля». Они определяют, как найти реакцию на исключение и что делать после вы полнения предписанных в ней действий.
Как уже сказано, именно на выбор этих правил влияет принцип минимальных
повреждений. Рассмотрим эти правила.
189
8.3.2. Распространение исключений. Принцип динамической ловушки
Итак, с каждым исключением может быть связана серия ловушек, содержащих реакции на исключение и расположенных в различных программных конструк тах. С учетом принципа минимальных повреждений в современных ЯП принят принцип динамического выбора ловушки – всегда выбирается реакция на возник шее исключение из ловушки, динамически ближайшей к месту «происшествия», то есть в режиме распространения исключения существенно используется дина мическая структура программы.
190
Современное состояние языков программирования
Другими словами, конкретные действия исполнителя зависят от динамиче ской цепочки вызовов, ведущей к тому программному конструкту, в котором воз никло исключение. Поэтому в соответствующей реакции появляется возмож ность учесть динамический, а не только статический контекст чрезвычайной ситуации. Это, конечно, помогает предотвратить распространение повреждений.
Поясним принцип динамической ловушки на примере фрагмента программы:
procedure Ð 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(«ОШИБКА в Р»);
-- другая реакция на то же исключение во второй ловушке
end Ð;
Если исключение «ошибка» возникнет на месте (3), то сработает реакция на это исключение во второй ловушке и будет напечатано «ошибка в Р». Если то же исключение возникнет на месте (2), то есть при вызове процедуры Q и не в R, то сработает реакция в первой ловушке и будет напечатано «ошибка в Q».
Пока подбор реакции как по динамической цепочке вызовов, так и по статиче ской вложенности конструктов дал одинаковые результаты.
А вот когда исключение «ошибка» возникает на месте (1) в теле процедуры Q (при вызове процедуры R, в которой ловушки нет), то отличие динамического выбора от статического проявляется наглядно. Статический выбрал бы реакцию из второй ловушки в теле Р, а динамический выберет реакцию из первой ловушки в теле Q.
Будет напечатано «ошибка в Q», что существенно точнее отражает суть слу чившегося. Именно для того, чтобы можно было точнее, конкретнее реагировать на исключение, и принят практически во всех ЯП принцип динамической ло вушки.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]