- •4.9.1. Изменяемость
- •4.9.2. Классы операций
- •4.9.3. Полнота
- •4.9.5. Операции egual, similar и copy
- •5. Исключительные ситуации
- •6.2.1. Сигнализация об исключительных ситуациях
- •5.2.2. Обработка исключительных ситуаций
- •5.2.3. Предложение resignal
- •6.2.4. Необрабатываемые исключительные ситуации
- •6.2.3. Предложение resignal
- •5.2.4. Необрабатываемые исключительные ситуации
- •6.2.1. Реализация итераторов
- •6.2.2. Использование итераторов
- •7.2. Абстракция данных
- •7.4. Генераторы
- •9.1.3. Пример
5. Исключительные ситуации
Исключительные ситуации.
ю1
Процедурная абстракция есть отображение аргументов в результаты с возможной модификацией некоторых аргументов. Аргументы принадлежат области определения процедуры, а результаты — области изменения. Если процедура реализуется на ориентированном на работу с типами языке (например, языке CLU), осуществляемый во время компиляции контроль типов гарантирует, что фактические аргументы действительно принадлежат области определения, а результаты — области изменения.
Часто процедура имеет смысл только для аргументов, принадлежащих подмножеству ее области определения. Например, операция choose для наборов целых чисел имеет смысл, только если ее аргумент—не пустой набор. Пока мы учитываем эту ситуацию, используя процедуру, заданную на некоторой части ее области определения. Например: choose = ргос (s: intset) returns (int) requires s — не пустой набор effects Возвращает произвольный элемент s.
Тот, кто обращается к такой процедуре, должен гарантировать, что аргументы принадлежат допустимому подмножеству области определения, и тот, кто реализует процедуру, может игнорировать аргументы, не принадлежащие этому подмножеству. Таким образом, реализуя операцию choose, мы игнорируем случай пустого набора.
Бывает, однако, что такие процедуры плохи. Компилятор не может гарантировать (в общем случае), что аргументы такой процедуры, как choose, действительно принадлежат допустимому подмножеству, и ввиду этого процедура может быть вызвана с аргументами, не принадлежащими этому подмножеству. Когда это происходит, возникает ошибка, которую очень трудно отследить. Например, программа может продолжать выполняться некоторое время после того, как произошла эта ошибка, и испортить важные базы данных.
Такие процедуры приводят к неустойчивым программам. Устойчивая программа — это такая программа, которая ведет себя корректно даже в случае ошибки. Конечно, в случае ошибки программа может быть неспособна вести себя точно так же, как
если бы не было ошибки, но она должна вести себя определенным,' предсказуемым образом. В идеальном случае она должна продолжать работать после ошибки, осуществляя некоторую аппроксимацию ее поведения при отсутствии ошибки. Говорят, что такая программа обеспечивает хорошую деградацию. В худшем случае) она должна остановиться с сообщением об ошибке, не испортив при этом никаких постоянных данных.
Метод, который гарантирует устойчивость, заключается в использовании процедур, заданных на всей области определения. Если процедура неспособна осуществить свою функцию для некоторых аргументов, она должна оповестить обратившегося к ней о возникшем затруднении. Таким образом, разрешение ситуации передается обратившемуся к процедуре, который может быть в состоянии что-то предпринять или избежать катастрофических последствий ошибки.
Конечно, проверка входных аргументов на принадлежность допустимому подмножеству области определения занимает время, и есть искушение не делать эти проверки или использовать их только при отладке и отключать при счете. Однако в общем случае это не самое мудрое решение. Лучше выработать привычку защитного программирования, т. е. составлять каждую программу так, чтобы она защищала саму себя от ошибок. Ошибки могут возникнуть из-за других процедур, из-за аппаратной части или из-за неправильного ввода данных пользователем; эти последние ошибки будут иметь место даже при отлаженном математическом обеспечении.
Защитное программирование облегчает отладку программ. При эксплуатации оно еще более ценно, так как исключает возможность того, что незначительная ошибка приведет к серьезной проблеме, как, например, к порче базы данных. Отключение контроля при эксплуатации аналогично отключению сигнальных огней на самолете; пилот никогда не пойдет на это, так как последствия могут быть катастрофическими. Проверка на нелегальные аргументы может быть отключена, только если мы точно знаем, что ошибки быть не может, или если проверка приводит к крайней неэффективности.
Как может быть оповещен обратившийся к процедуре, если возникла проблема? Одна из возможностей — это определить специфический результат, возвращаемый в случае ошибки. Например, процедура факторизации может возвратить нуль, если ее аргумент не положительное число:
fact = ргос (п: int) returns (int) effects Если п->0 возвращает п!, иначе—0.
Однако это не очень хорошее решение. Так как обращение к процедуре с нелегальными аргументами, вероятно, является
Исключительные ситуации
108 Глава 5
ошибкой, более конструктивно рассматривать этот случай особо, чтобы использующему данную процедуру программисту было труднее игнорировать эту ошибку по невнимательности. Кроме того, если каждое значение возвращаемого типа есть возможный результат процедуры (как в случае с процедурой choose), то это решение не приводит к положительному результату, так как нет значения, с помощью которого можно было бы оповестить об ошибке. Предпочтительнее был бы метод, который не зависел бы от того, есть такое значение или нет.
Такой метод может быть получен через обобщение процедур. Вместо того чтобы иметь одну область определения, мы разделим область определения D на некоторое число подмножеств, объединение которых дает D:
D=DoU^U...UDn
Каждое подмножество «особо» в том смысле, что процедура ведет себя по-разному на каждом из них. Например, для операции choose мы можем разделить область определения на два подмножества:
Do = ^все непустые наборы^ Di == {пустой набор}
Если аргумент принадлежит Do, процедура choose, как и раньше, возвращает выбранное целое число. Если аргумент принадлежит Di, также осуществляется возврат из процедуры, но так, что пользователь оповещается о возникшей проблеме. Как мы уже отмечали, невозможно оповестить пользователя, используя целое число, так как любое целое число является потенциальным результатом для какого-то аргумента из Do. Следовательно, необходим другой метод оповещения.
Мы обеспечим оповещение, имея различные области изменения для каждого подмножества области определения и придав различным случаям различные имена. Произвольно дадим случаю аргумента из Do имя «обычный». Имена для других случаев могут быть выбраны тем, кто определяет процедуру. Таким образом, имеем р: do —>- обычный (ro) di -»- ИМЯ1 (ri)
Dn -^ имя„ (Rn)
где имя^, ..., имяц — имена «исключительных» ситуаций, соответствующих аргументам, принадлежащим Di,... Dn соответственно, а Но, ... Rn описывает число, порядок и типы результатов, возвращаемых в каждом случае. Имя^ ..., имяп называются исключительными ситуациями. Например, имеем
choose: Do ->• обычный (int) Di ->- empty ( )
. где Pi пусто, так как в этом случае пользователю не возвращается никакого результата.
В этой главе мы будем говорить о том, как специфицировать, реализовывать и использовать процедуры с исключительными ситуациями. Рассмотрим также некоторые связанные с этим темы.
р 5.1. Спецификации
Л Чтобы специфицировать процедуры, которые вызывают исключительные ситуации, добавим в спецификацию предложение signals:
signals % здесь приводится список имен и результатов исключительных ситуаций
Это предложение — часть заголовка. Оно следует за предложением returns. Если исключительных ситуаций не имеется, оно может быть опущено. Исключительные ситуации должны быть разделены запятыми, а их результаты (если такие имеются) должны быть заключены в скобки. Например, предложение
choose = proc (i: intset) returns (int) signals (empty)
говорит о том, что процедура choose может вызывать исключительную ситуацию empty и что в этом случае не возвращается никакого результата. Предложение же
search = proc (a: array [int], х: int) returns (ind: int) signals (not-in, duplicate (indi: int))
говорит о том, что процедура ^earch может вызывать две исключительные ситуации: not-in /(без результата) и duplicate (результат—целое число). Имя indi вводится для ссылок на этот результат в остальной части спецификации.
Как и раньше, секция effects должна определять поведение процедуры для всех фактических аргументов, отвечающих предложению requires. Так как это поведение включает в себя исключительные ситуации, секция effects должна определять, что приводит к вызову каждой исключительной ситуации и что делает процедура в каждом таком случае. Если процедура сигнализирует об исключительной ситуации для аргументов, принадлежащих D;, возможные значения аргументов, принадлежащие D,, не должны Напутствовать в предложении requires. Завершение процедуры оповещением об исключительной ситуации — это один из нормальных режимов работы этой процедуры.
110 Глава 5
На рис. 5.1 показаны спецификации процедур choose и search.
choose = proc (s: intset) returns (int) signals (empty)
effects Если size (s) = 0, то сигнализировать об исключительной ситуации empty, иначе возвратить произвольный элемент s.
search = proc (a: array [int], х: int) returns (ind: int) signals (notJn, duplicate (iridi: int))
requires Массив а упорядочен в возрастающем порядке. effects Если х ^ а один раз, то возвратить ind, такой, что a find] == х; Если х ^ а более чем один раз, то сигнализировать об исключительной ситуации duplicate (indi), где indi —индекс для одного из х; иначе— сигнализировать об исключительной ситуации not_in.
Рис. 5.1. Спецификации с исключительными ситуациями.
Заметим, что спецификация процедуры search содержит предложение requires и что, как обычно, в секции effects предполагается, что все требования предложения requires удовлетворены. Область определения процедуры search есть
Do = {(а, х> di = {(а, х D» = {(а, х<)
х ^ а ровно один раз) х^а) х $ а более чем один раз}
Здесь подмножества области определения стоящие из массива и целого числа.
это пары, со-
5.2. Механизм исключительных ситуаций в языке CLU
Язык программирования может сделать работу о исключительными ситуациями легкой, если он включает в себя механизм исключительных ситуаций. Этот механизм должен обеспечивать для процедуры возможность сигнализировать об исключительных ситуациях, т. е. доводить их возникновение до сведения использующего процедуру. Кроме того, эту информацию должна быть способна получать вызывающая процедура, чтобы обрабатывать исключительные ситуации. Однако вызывающая процедура не должна обрабатывать исключительные ситуации, которые не могут возникнуть. Например, предположим, что мы выполняем следующую операцию:
И intset$size (s) £> 0 then х := intset$choose (s) end
Будет странно, если обращение к процедуре choose сигнализирует нам об исключительной ситуации empty. Для программиста нет необходимости обрабатывать исключительные ситуации в подобных случаях.
Исключительные ситуации