Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
38
Добавлен:
23.03.2015
Размер:
884.22 Кб
Скачать

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. Для программиста нет необходимости обрабатывать исключительные ситуации в по­добных случаях.

Исключительные ситуации

Соседние файлы в папке POSIBNIK