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

6.2.3. Предложение resignal

Иногда обработка исключительной ситуации сводится просто к сигнализации о той же самой исключительной ситуации с теми же самыми результатами. Для этого случая в языке CLU имеется предложение resignal

resignal имена

По существу это является короткой формой предложения excepts в котором каждое имя сигнализируется явно и с теми же самыми результатами. Например,

i := search (a, x) resignal duplicate, not_in

является короткой формой для

i :== search (a, x) except when not_in: signal not_in when duplicate (j: int): signal duplicate (j) end

5.2.4. Необрабатываемые исключительные ситуации

Программы на языке CLU не обязательно должны включать в себя программы обработки для всех исключительных ситуаций. Вместо этого каждая процедура на языке CLU имеет еще одну дополнительную исключительную ситуацию, кроме тех, которые перечислены в списке предложений signals. Эта исключительная ситуация имеет имя failure и один аргумент, строку. Каждая исключительная ситуация, возникшая в вызванной процедуре

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

и не обрабатывающаяся явно в вызывающей процедуре, автома­тически преобразуется в исключительную ситуацию failure. Это преобразование эквивалентно действиям, которые были бы про­изведены следующим включенным в тело процедуры предложе­нием except:

except when failure (s: string): signal failure (s) others (s: string): signal failure («необрабатыв. искл. сит.: " Ц s) end

Если необрабатываемая исключительная ситуация не является failure, ее имя становится частью строкового аргумента; если необрабатываемая исключительная ситуация есть failure, ее стро­ковый аргумент сохраняется. Таким образом, строковый аргумент содержит имя первой необрабатываемой исключительной си­туации

~ Реализация процедуры choose (рис. 5.2) включает в себя необрабатываемую исключительную ситуацию. Так как мы знаем, что при выполнении оператора return набор s не пуст, мы не беспокоимся о возникновении исключительной ситуации bounds. В случае же, когда эта исключительная ситуация все-таки воз­никает, она автоматически распространяется как исключитель­ная ситуация failure («необрабатыв. искл. сит.: boudns»). (Заме­тим, что исключительная ситуация boudns может возникнуть только в случае ошибки в реализации массивов.)

Оператор others предложения except рассматривает исключи­тельную ситуацию failure иначе, чем другие исключительные ситуации. Если исключительная ситуация, попавшая в ветвь

others (s: string): тело

есть failure, то s присваивается строковый аргумент исключи­тельной ситуации failure, а не строка «failure». Эффект в этом случае такой же, как если бы ветвь оператора others была разбита на две следующие части:

when failure (s: string): тело others (s: string): тело

Об исключительной ситуации failure также может быть сигна­лизировано явно; в этом случае должен быть задан строковый аргумент.

Должны обрабатываться все исключительные ситуации, кроме тех, которые не могут возникнуть. Следовательно, ситуация failure обычно означает, что произошла программная или систем­ная ошибка. В этом случае ситуацию обычно анализирует про­граммист. Информация в строке исключительной ситуации failure предназначена главным образом для программистов, а не §ля про­грамм.

116 Глава 5

6.2.5. Предложение exit

В гл. 2 мы ввели предложения break и continue; break исполь­зуется для выхода из цикла, a continue — для перехода к следую­щей итерации цикла. Иногда необходима более общая передача управления. Эту возможность предоставляют предложения exit и except, используемые совместно.

Форма предложения exit похожа на форму предложения signal exit имя (выражение)

Имя определяет выходную точку предложения exit; это имя должно встречаться в предложении, содержащем except, и управ­ление передается ближайшему предложению, содержащему except, с программой обработки, соответствующей этому имени. Если при выполнении предложения exit никаких значений не пере­дается, часть (выражение) может отсутствовать.

В следующем примере предложение exit используется для завершения цикла поиска.

ai == array [int] х: int i: int :== ai$low (a) begin while i <= ai$high (a) do x:=a[i]

if special (х) then exit found end i := i + I end

x :== make. new-one () % если не находит, создает новый end except when found: end % Здесь мы имеем нужное значение для х

5.3. Использование исключительных ситуаций в программах

При реализации процедуры с исключительными ситуациями работа программиста заключается в том, чтобы обеспечить соот­ветствующую спецификации работу процедуры. Если специфи­кация включает в себя исключительные ситуации, программа должна в нужное время сигнализировать о соответствующих исключительных ситуациях и передавать определенные в специ­фикации значений. Для выполнения этой задачи программа, возможно, должна будет обрабатывать исключительные ситуации, возникающие в процедурах, к которым она обращается,

Исключительные ситуации могут обрабатываться двумя раз­личными способами. Иногда исключительная ситуация распро­страняется до другого уровня, т. е. вызывающая процедура также завершает работу сигнализацией об исключительной си­туации с тем же самым или другим именем. Перед распростране-

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

нием исключительной ситуации вызывающая процедура может осуществить некоторую локальную обработку. Такая обработка иногда необходима для достижения соответствия со специфика­цией вызывающей процедуры. Например, операция типа данных должна гарантировать, что объекты перед ее завершением будут удовлетворять инварианту представления.

Другой способ заключается в том, что вызывающая процедура маскирует исключительную ситуацию, т. е. обрабатывает исклю­чительную ситуацию сама. Например, процедура может считы­вать все символы потока, используя операцию stream$getc. Когда процедура getc сигнализирует о конце файла (end-ol'-file), это просто означает, что все символы считаны и пора закончить работу.

В этом разделе представлены примеры использования исклю­чительных ситуаций при спецификации и реализации процедур. Наш первый пример — наборы целых чисел intset. Эти наборы — те же, что и раньше (рис. 5.4), за исключением того, что операция intset == cluster is create, insert, delete, member, size, choose rep -= array [int] create = proc ( ) returns (cvt) return (rep$new ( )) end create

insert == proc (s: intset, х: int) if— member (s, х) then rep$addh (down (s), х) end end insert delete == proc (s: cvt, х: int)

j: int := getind (s, х) ехсерт when not_in: return end s [j] := rep$top (s) rep$remh (s) end delete

member == proc (s: cvt, х: int) returns (bool) getir)j (s, х) except when not_in: return (false) end return (true) end member size = proc (s: cvt) returns (int) return (rep$size (s)) end size

choose = proc (s: cvt) returns (int) signals (empty) return (rep$bottom s))

except when bounds: signal empty end end choose

getind == proc (s: rep, х: int) returns (int) signals (not_in) i: int := rep$low (s) while true do if x == s [i] then return (i) end

except when bounds: signal not_in end i:=i+ I end end getind end intset Рис. 5.4. Реализация типа данных intset.

118 Глава 5

choose сигнализирует об исключительной ситуации, если ее аргу­мент пуст. Реализация представлена на рис. 5.4. Заметим, что мы изменили операцию getind, чтобы она сигнализировала об исключительной ситуации not-in, если х не принадлежит s. Этот сигнал перехватывается операцией delete, которая просто маскирует исключительную ситуацию и осуществляет нормальный возврат. Реализация операции member также маскирует исклю­чительную ситуацию.

Реализация операции getind использует исключительную си­туацию bounds, о которой сигнализирует операция fetch для массивов, в случае если она просмотрела все элементы массива. Когда процедура getind получает этот сигнал, она сигнализирует об исключительной ситуации not_in, что является примером распространения исключительной ситуации. Другой пример рас­пространения — процедура choose.

Реализация операции delete иллюстрирует использование не­обрабатываемых исключительных ситуаций. Если из процедуры getind осуществляется нормальный возврат, мы знаем, что индекс находится внутри заданных границ и что массив не пуст. Следова­тельно, нет необходимости следить за возникновением исключи­тельной ситуации bounds при вызове операций top, fetch и remh для массивов,

Следующий пример —это упорядоченные списки. Удалив не­которые предложения requires и добавив исключительные ситуа­ции в операциях addel, remel и least, мы получаем более устойчи­вую спецификацию. Эта результирующая спецификация пред­ставлена на рис. 5.5.

Реализация упорядоченных списков показана на рис. 5.6. Некоторые процедуры используют предложение resignal. Напри­мер, процедура addel рекурсивно обращается сама к себе по левому и правому поддеревьям; если в каком-то из этих обраще­ний возникает исключительная ситуация dupl, происходит вы­полнение предложения resignal. Это предложение часто исполь­зуется в рекурсивных реализациях, как, например, в нашем случае.

В качестве последнего примера рассмотрим процедуру sum­mation, специфицированную и реализованную на рис. 5.7. Заме­тим, что в предложениях requires и where перечислены исклю­чительные ситуации, связанные с процедурой add. Процедура summation использует списки, определенные на рис. 4.12, 'за исключением того, что процедуры first и rest сигнализируют об исключительной ситуации empty, если список, заданный как аргумент, пуст. Исключительная ситуация empty, возникающая при вызове list.$first, маскируется; когда она возникает, все элементы списка уже просмотрены. Исключительные ситуации underflow и overflow распространяются.

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

olist^data type It; type] is create, addel, remel, ii.in, empty, least

Requires t имеет операции

It, equal: proctype (t, t) returns (bool), которые определяют упорядочение t

empty

Описание

Упорядоченные списки olist — изменяемые списки элементов. Операции addel и remel модифицируют упорядоченный список. Операция least возвращает наи­меньший элемент упорядоченного списка,

Операции

create = ргос ( ) returns (olist [t ]) effects Возвращает новый, пустой список.

addel г= ргос (s: olist [t], х: t) signals (dup!)

modifies s

effects Если х принадлежит s, сигнализирует об исключительной си­туации dupl, иначе вставляет х в s.

remel == ргос (s: olist [t], х: t) signals (notJn)

modifies s

effects Если х не принадлежит s, сигнализирует об исключительной ситуации not_in, иначе удаляет х из s.

is_in = ргос (s: olist [tl, х: t) returns (bool)

effects Возвращает значение true, если s содержит какой-нибудь эле­мент, равный х, в противном случае возвращает значение false.

ргос (s: olist [t]) returns (bool)

effects Возвращает значение true, если s не содержит элементов, и зна­чение false — в противном случае.

least =- ргос (s: olist [t]) returns (t) signals (empty) effects Если s пуст, сигнализирует об исключительной ситуации empty, иначе возвращает элемент е из s, такой, что в s не существует эле­мента, меньшего е (как определяется t$lt).

end olist Рис. 5.5. Спецификация упорядоченных списков.

olist == cluster [t: type] is create, addel, remel, isJn, least, empty where t has equal. It: proctype (t, t) returns (bool)

node == record [val: t, left, right: olist [t]] rep= variant [some: node, empty: null]

% Типичный упорядоченный список olist есть [el, ...,en] % Функция абстракции есть

% A (r) = [] если г пуст, % = A (n.left) [[ [n.val ] \\ A (n.right) % где n = value_some (r)

% Инвариант представления есть % if is_some (r) then % ace компоненты n.left < n.val % & n.val < всех компонентов n.right, % где n == value_some (r)

если если r не пуст,

118 Глава 5

choose сигнализирует об исключительной ситуации, если ее аргу­мент пуст. Реализация представлена на рис. 5,4. Заметим, что мы изменили операцию getind, чтобы она сигнализировала об исключительной ситуации not-in, если х не принадлежит s. Этот сигнал перехватывается операцией delete, которая просто маскирует исключительную ситуацию и осуществляет нормальный возврат. Реализация операции member также маскирует исклю­чительную ситуацию.

Реализация операции getind использует исключительную си­туацию bounds, о которой сигнализирует операция fetch для массивов, в случае если она просмотрела все элементы массива. Когда процедура getind получает этот сигнал, она сигнализирует об исключительной ситуации not_in, что является примером распространения исключительной ситуации. Другой пример рас­пространения — процедура choose.

Реализация операции delete иллюстрирует использование не­обрабатываемых исключительных ситуаций. Если из процедуры getind осуществляется нормальный возврат, мы знаем, что индекс находится внутри заданных границ и что массив не пуст. Следова­тельно, нет необходимости следить за возникновением исключи­тельной ситуации bounds при вызове операций top, fetch и remh для массивов.

Следующий пример —это упорядоченные списки. Удалив не­которые предложения requires и добавив исключительные ситуа­ции в операциях addel, remel и least, мы получаем более устойчи­вую спецификацию. Эта результирующая спецификация пред­ставлена на рис. 5.5.

Реализация упорядоченных списков показана на рис. 5.6. Некоторые процедуры используют предложение resignal. Напри­мер, процедура addel рекурсивно обращается сама к себе по левому и правому поддеревьям; если в каком-то из этих обраще­ний возникает исключительная ситуация dupl, происходит вы­полнение предложения resignal. Это предложение часто исполь­зуется в рекурсивных реализациях, как, например, в нашем случае.

В качестве последнего примера рассмотрим процедуру sum­mation, специфицированную и реализованную на рис. 5.7. Заме­тим, что в предложениях requires и where перечислены исклю­чительные ситуации, связанные с процедурой add. Процедура summation использует списки, определенные на рис. 4.12, 'за исключением того, что процедуры first и rest сигнализируют об исключительной ситуации empty, если список, заданный как аргумент, пуст. Исключительная ситуация empty, возникающая при вызове list$first, маскируется; когда она возникает, все элементы списка уже просмотрены. Исключительные ситуации underflow и overflow распространяются.

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

olist=data type [t: type I is create, addel, remel, is_in, empty, least

Requires t имеет операции

It, equal: proctype (t, t) returns (bool), которые определяют упорядочение t

Описание

Упорядоченные списки olist — изменяемые списки элементов. Операции addel и remel модифицируют упорядоченный список. Операция least возвращает наи­меньший элемент упорядоченного списка,

Операции

create = ргос () returns (olist [t]) effects Возвращает новый, пустой список.

addel = proc (s: olist [t], х: t) signals (dupl)

modifies s

effects Если х принадлежит s, сигнализирует об исключительной си­туации dupl, иначе вставляет х в s.

remel = proc (s: olist [t], х: t) signals (notJii)

modifies s

effects Если х не принадлежит s, сигнализирует об исключительной ситуации not-in, иначе удаляет х из s.

isJn = ргос (s: olist [t], х: t) returns (bool)

effects Возвращает значение true, если s содержит какой-нибудь эле­мент, равный х, в противном случае возвращает значение false.

empty = ргос (s: olist [t]) returns (bool)

effects Возвращает значение true, если s не содержит элементов, и зна­чение false — в противном случае.

least = proc (s: olist [t]) returns (t) signals (empty) effects Если s пуст, сигнализирует об исключительной ситуации empty, иначе возвращает элемент е из s, такой, что в s не существует эле­мента, меньшего е (как определяется l$lt).

end olist Рис, 5.5. Спецификация упорядоченных списков.

olist ^cluster [t: type] is create, addel, remel, is-in, least, empty where t has equal, It; proctype (t, t) returns (bool)

node == record [val; t, left, right: olist [t]) rep == variant [some; node, empty; null I

% Типичный упорядоченный список olist есть [el, ...,en] % Функция абстракции есть

% А (г) = [] если г пуст, % == A (n.left) II [n.val ] Ц A (n.right) если г не пуст, % где п == value-some (г)

% Инвариант представления есть % if is-some (r) then % все компоненты n.left < n.val % & n.val < всех компонентов n.right, % где n = value.some (r)

120 Глава 5

create = proc ( ) returns (cvt) return (rep$make_empty (nil)) end create

addel = proc (s: cvt, v; t) signals (dupl) tagcase s fag some (n: node): it v < n.val then signal dupl elseif v < n.val then addel (n.left, v) else addel (n.right, v) end resignal dupl tag empty:

rep$change_some (s, tiode${val: v, left: create (), right: create ())) end end addel

rernel = proc (s: cvt, v: t) signals (notJn) tagcase s tag empty: signal notJn tag some (n: node): if v == n.val then if empty (n.right) then % заменить узел на лево? поддерево rep$v_get_v (s, down (n. left))

else % сделать n.val значением из правого поддерева n.val :== least (n.right) remel (n.right, n.val) end

elseif v < n.val then remel (n.left, v) else remel (n.right, v) end resignal not_in end end remel

isJn = proc (s: cvt, v: t) returns (bool) tagcase s tag empty: return (false) tag some (n: node): if v = n.val then return (true) elseif v < n.val then return (isJn (n.left, v)) else return (isJn (n.right, v)) end end end isJn

least = proc (s: cvt) returns (t) signals (empty) tagcase s tag empty: signal empty tag some (n: node): return (least (n.left)) end except when empty: return (n.val) end end least

empty = proc (s: cvt) retunis (bool) return (rep$is_empty (s)) end empty

end olist Рис. 5.6, Реализация упорядоченных списков.

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

summation^ proc [t: type] (x: iist [t], zero; t) returns (t)

signals (underflow, overflow) requires t имеет операцию добавления:

add: proctype(t, t) returns (t) signals (underflow, overflow) effects Создает сумму элементов в x, начиная с zero; сигнализирует об искл. ситуациях underflow или overflow в случае возникновения потери значимости или переполнения при вычислении суммы.

summation = proc [t: type] (x: list (t], zero: t) returns (t)

signals (underflow, overflow) where t has add: proctype (t, t) returns (t)

signals (underflow, overflow) sum: t : == zero while true do sum ;= sum-{- list [t]$first (x) % используется t$add

except when empty: return (sum) end x := list [t]$rest (x) end resignal underflow, overflow end summation

Рис. 5.7. Процедура summation.

5.4. Некоторые аспекты проектирования программ

Исключительные ситуации следует использовать для устране­ния большинства ограничений, перечисленных в предложениях requires. Эти предложения следует оставлять только из сообра­жений эффективности или если контекст использования настолько ограничен, что мы можем быть уверены, что ограничения удов­летворяются. Например, процедура search будет, вероятно, тре­бовать, чтобы массив был упорядочен, так как в этом случае она может быть реализована намного более эффективно. Точно так же процедура merge, используемая для сортировки слиянием (рис. 3.2), будет требовать, чтобы ее аргументы были упорядочены, так как, с одной стороны, это улучшает эффективность и, с дру­гой стороны, потому что контекст ее использования ограничен.

Исключительные ситуации также следует использовать для того, чтобы избежать кодирования информации в обычных ре­зультатах. Например, процедура getind кластера набора целых чисел (рис. 5.4) сигнализирует об исключительной ситуации, если элемент не находится в массиве, а не возвращает элемент, боль­ший, чем верхняя граница. Лучше передавать эту информацию с помощью исключительной ситуации, так как возвращаемый в этом случае результат не может быть использован как обычный результат. Используя исключительную ситуацию, мы можем легко отличать этот результат от обычного, что позволяет избе­жать потенциальной ошибки.

Когда процедура имеет смысл только для аргументов, при­надлежащих подмножеству ее области определения, в реализации этой процедуры допускается делать с не принадлежащими этому подмножеству аргументами все, что угодно. Конечно, не все

122 Глава 5

реализации равно хороши. Самый лучший подход — сигнализи­ровать об исключительной ситуации failure. Часто это происхо­дит естественным образом, либо из-за того, что не обрабатывается исключительная ситуация, которая не имеет места для аргумен­тов, принадлежащих допустимому подмножеству, либо из-за выхода на конец процедуры, которая должна возвращать резуль­таты. (В этом случае компилятор языка CLU вставляет команды для сигнализации об исключительной ситуации failire.) В других случаях, возможно, стоит затратить некоторые усилия. Напри­мер, в процедуре merge (рис. 3.6) мы можем во время просмотра элементов массива сравнивать эти элементы с целью определить, упорядочены они или нет, и если нет, то мы сигнализируем об исключительной ситуации failure. Такие проверки — некоторая дополнительная работа, однако они существенно улучшают устой­чивость программы.

Не все ошибки сызывают исключительные ситуации. Напри­мер, пусть в большом вводном файле имеется ошибочная запись и будет возможно продолжать обработку файла, пропустив эту запись. В этом случае целесообразно оповес_ти_гь_..иол.ьзователя (а не программу) о произошедшей^ошибке. Исключительные ситуа-ций^являются механизмом взаимодействия программ, а не про­грамм с пользователями. Для взаимодействия с пользователями па какое-нибудь устройство вывода может быть выдано сообщение об ошибке. Заметим, что реакция на ошибку определяется в спе­цификации абстракции.

Однако исключительные ситуации не всегда связаны с ошиб­ками. Для некоторых абстракций может быть более чем один тип обычного поведения процедур, и в этом случае исключительные ситуации — удобный инструмент. Они предоставляют средства для обеспечения нескольких типов поведения и дают возможность вызывающему процедуру различать между различными случаями.

Например, операция lookup над таблицей символов имеет две цели. Согласно заданному идентификатору, она определяет, обрабатывалось ли уже объявление этого идентификатора и если да, то возвращает информацию об этом идентификаторе. Заголовок для этой операции может быть следующим:

lookup =proc(s: symbol-table, id: string) returns (info) signals (noL in)

Здесь мы решили рассматривать случай, в котором объявле­ние существует как обычный случай, но мы можем легко принять противоположное решение. При принятии решения следует учи­тывать, в частности, эффективность. Обычно исключительные си­туации требуют больших затрат, чем обычный возврат. (Это верно для языка CLU, хотя различие и мало.) Следовательно, случай, который предположительно будет возникать чаще, нужно рас-

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

сматривать как обычный случай. Если решение о том, какой слу­чай будет обычным, принимается из соображений производитель­ности, то очевидно, что с исключительной ситуацией не должно быть связано никаких понятий типа «ошибка».

Стоит поговорить о взаимосвязи модификации аргументов с за­вершением работы процедуры выходом на исключительную ситуа­цию. Секция modifies спецификации указывает, что аргумент может модифицироваться, но не говорит о том, когда это проис­ходит. Если имеются исключительные ситуации, то обычно мо­дификации происходят только для каких-нибудь из них. Что в точности происходит, должно быть описано в секции effects. Модификации должны быть описаны явно в каждом случае, в ко­тором они совершаются; если не описано никаких модификаций, это означает, что они не совершаются вообще. Например, рас­смотрим процедуру

addel == proc (s: olist [t], x: t) sinals (dupl) modifies s

effects Если x уже принадлежит s, сигнализировать об исключ. ситуации dupl, иначе включить х в s,

Так как для случая, когда процедура addel сигнализирует об исключительной ситуации dupl, не описано никаких модификаций, s модифицируется только при обычном возврате из процедуры addel,

Исключительная ситуация failure является неявной исключи­тельной ситуацией каждой процедуры, однако она не упоми­нается ни в каких спецификациях. И это вполне оправданно. Спецификация описывает поведение процедуры при ее работе и для случая, когда аргументы удовлетворяют предложению re­quires. Исключительная ситуация failure возникает тогда, когда в работе процедуры происходит сбой и она больше не отвечает своей спецификации или когда заданы неверные аргументы. .

Обычно исключительная ситуация failure не может быть об­работана программой. Эта ситуация означает либо ошибку в ма­тематическом обеспечении, либо сбой в аппаратной части. Очень устойчивая программа с анализом всевозможных ситуаций может быть способна заблокировать ошибочную часть и продолжать работать с остальным. В этом случае полезно внести ошибку в про­токол, чтобы ее можно было исправить впоследствии. Менее устой­чивая программа просто завершит свою работу и внесет ошибку в протокол. (Если программа выполняется в режиме отладки, пользователь у консоли может быть оповещен немедленно.)

Исключительные ситуации, о возникновении которых сигна­лизируют операции типов данных, связаны со специфическими операциями. Однако имена исключительных ситуаций относятся к типу в целом: либо к статусу объектов типа (например, список

124 Глава 5

может быть пуст или не пуст), либо к тому факту, что такой объект не существует (например, существует наименьшее и наибольшее целое). Следовательно, операции типа должны использовать оди­наковые имена исключительных ситуаций для одинаковых слу­чаев. Например, операции для списков first и rest сигнализируют об исключительной ситуации empty, если список, заданный в ка­честве аргумента, пуст, а операции для целых чисел, результатом которых является слишком большое число, сигнализируют об исключительной ситуации overflow.

Исключительные ситуации предоставляют информацию, кото­рая обычно может быть получена прямым обращением к опера­циям. Например, о факте пустого списка можно узнать, либо обратившись к операции first и получив ее сигнал, либо обратив­шись к операции empty. Такая избыточность не необходима. Например, возможен тип, в котором узнать, пуст объект или не пуст, можно только через исключительную ситуацию.

В наших примерах реализаций мы не стремились избегать возникновения исключительных ситуаций; наоборот, мы исполь­зовали их для управления работой программ. Исключительные ситуации могут улучшить производительность за счет уменьшения количества вызовов процедур. Например, реализация процедуры choose (рис. 5.4) использует исключительную ситуацию bounds для того, чтобы избежать обращения к операции size для массивов.

5.5. Заключение

В этой главе мы обобщили процедуры, введя исключительные ситуации. Исключительные ситуации необходимы для создания устойчивых программ, так как предоставляют способ реагировать на ошибки. Если аргумент не тот, который ожидается, процедура может оповестить обратившегося к ней об этом факте, вместо того чтобы просто выйти на сбой. Так как это оповещение отли­чается от обычного случая, обратившийся к процедуре не может их перепутать.

Исключительные ситуации вводятся при разработке процедур. Большинство процедур должно быть задано на всей области опре­деления; исключительные ситуации используются для обработки ситуаций, в которых «обычная» работа программы не может быть осуществлена. Частичные процедуры используются только по соображениям эффективности или когда процедура исполь­зуется в ограниченном контексте, о котором точно известно, что все обращения имеют корректные аргументы. В любом случае при реализации процедуры полезно практиковать защитное про­граммирование, сигнализируя об исключительной ситуации fai­lure там, где только возможно,' при значениях аргументов, не принадлежащих принятому подмножеству области определения.

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

При реализации процедуры программист должен гарантиро­вать, что она во всех ситуациях закончится в соответствии со спецификацией. Следует сигнализировать только об исключи­тельных ситуациях, указанных в спецификации. Сигнализировать следует, кроме того, только по правильным причинам. При созда­нии реализации разумно использовать исключительные ситуации вызываемых процедур и в зависимости от обстоятельств распро­странять или маскировать их.

Дополнительная литература

Goodenough, John В., 1975. Exception handling: issues and proposed nota­tion. Communications of the ACM 18 (12): 683—696.

Liskov, Barbara, H., Alan Snyder, 1979. Exception handling in CLU. IEEE Transactions on Software Engineering SE-5 (6): 546—558.

Упражнения

5.1. Модифицируйте абстракцию данных poly, определенную в гл. 4 (рис. 4.3), чтобы воспользоваться преимуществами исключительных ситуаций. Специфицируйте новую абстракцию и затем реализуйте ее.

5.2. Модифицируйте абстракцию данных list, определенную в гл. 4 (рис. 4.12), чтобы воспользоваться преимуществами исключительных ситуаций. Специфи­цируйте новую абстракцию и затем реализуйте ее.

5.3. Реализуйте процедуру remove, dup! в терминах упорядоченных спи­сков, воспользовавшись исключительными ситуациями, о которых оповещают операции типа данных olist (рис. 5.5).

5.4. Упр. 9 гл. 4 связано с абстракцией ограниченных очередей. Переопре­делите эту абстракцию, используя исключительные ситуации, и реализуйте модифицированную абстракцию.

5.5. Отображение — это таблица, связывающая элементы (некоторого про­извольного типа) со строками. Каждая строка отображается максимально в один связанный с ней элемент. Операции над отображением включают в себя create (для создания пустого отображения), insert (для добавления строки и связанного с ней элемента), change (для изменения элемента, связанного со строкой), delete (для удаления строки и связанного с ней элемента) и eval (для нахождения эле­мента, связанного со строкой). Специфицируйте отображения, используя соот­ветствующие исключительные ситуации, и сконструируйте дополнительные операции, необходимые для полноты. Затем реализуйте вашу спецификацию и обеспечьте инвариант представления и функцию абстракции. Ваша реализация должна быть эффективна: операция eval должна выполняться быстрее, чем за п шагов, где п — число входных точек отображения.

5.6. Обсудите полноту абстракции отображений, определенной в упр. 5. Возможно ли всегда избегать обращений к вызывающим исключительные ситуа­ции операциям над отображениями при помощи, например, обращений к другим операциям над отображениями? Обсудите, правильные ли в этом отношении были приняты решения.

5.7. Процедура для вычисления минимального значения массива может требовать непустой массив. В случае пустого массива эта процедура возвращает наименьшее целое число или сигнализирует об исключительной ситуации. Обсу­дите, какая альтернатива лучше.

5.8. Предположим, что вместо вычисления минимального значения про­цедура из упр. 7 складывает элементы массива. Изменит ли это вашу идею о том, какая альтернатива лучше?

6. Абстракция итерации

В этой главе обсуждается наш последний механизм абстрак< ции — абстракция итерации, или, коротко, итератор. Итера­торы являются некоторым обобщением итерационных методов, имеющихся в большинстве языков программирования. Они поз­воляют пользователям выполнять итерации для произвольных типов данных удобным и эффективным способом.

Например, использование некоторого набора обычно состоит в том, чтобы выполнить некоторые действия для каждого его элемента.

for all элементов набора do действие

Такой цикл мог бы быть выполнен полностью для всего набора, например чтобы напечатать все элементы набора. Или мы могли бы искать некоторый элемент, который удовлетворяет некоторому условию, и в этом случае такой цикл может остановиться, как только будет найден требуемый элемент.

Наборы intset, как мы их определили до сих пор, не дают удобного способа для выполнения таких циклов. Например, предположим, что мы хотим вычислить сумму элементов в неко­тором наборе intset.

setsum == proc (s: intset) returns (int) effects Выдает сумму элементов в s.

Реализация процедуры setsum, показанная на рис. 6.1, иллю­стрирует два основных недостатка в нашей абстракции набора intset. Во-первых, для того чтобы выполнить цикл по всем эле­ментам, мы удаляем каждый элемент, возвращенный операцией choose, так что этот элемент не будет снова выбран. Таким обра­зом, на каждой итерации должны быть обращения к двум опера­циям — choose и delete. Этой неэффективности можно было избежать, если бы заставить операцию choose удалять выбранный элемент. Но мы все равно имеем вторую проблему, которая со­стоит в том, что итерация по всему набору intset уничтожит этот набор, поскольку удаляются все его элементы. Иногда такое уничтожение бывает приемлемым, но оно не может быть удовле­творительным в общем случае. Хотя мы можем собрать удаленные

Абстракция итерации

ai = array [int] setsum = proc (s: intset) returns (int) a; ai :== ai$new ( ) % Вычислить сумму sum: int := 0 while true do x: int := intset$choose (s) sum :== sum+ x intsel$delete (s, x) ai$addh (a, x) end except when empty: end % Восстановить элементы s i: int :=. I while true do

intset$insert(s,ati]) excepr when bounds: return (sum) end i:-i+ I end end setsum

Рис. 6.1. Реализация процедуры setsum.

элементы и потом их опять вставить в набор, как это делается на рис. 6.1, такой метод является неуклюжим и неэффективным.

Если бы процедура setsum являлась некоторой операцией над набором intset, мы могли бы реализовать ее эффективно при по­мощи операций над массивами. Однако процедура setsum не имеет реального смысла как некоторая операция над набором intset, она представляется несколько в стороне от концепции набора. Более того, даже если бы мы могли обосновать создание такой операции, то что же делать с другими аналогичными процедурами, которые нам могут понадобиться, например, с такой, как печать всех элементов? Должен быть некоторый способ реализации таких процедур помимо введения типа данных.

Для адекватного решения проблемы итерации нам нужен эффективный доступ ко всем элементам некоторого множества без уничтожения этого множества. Как мы могли бы это сделать для наборов intset? Создать такую операцию el_seg:

el.seg == proc (s: intset) returns (seg [int])

effects Возвращает некоторую последовательность, содержащую все элементы s в некотором произвольном порядке, причем каждый элемент — только один раз.

Если задана эта операция, мы можем реализовать процедуру setsum так, как показано на рис. 6.2. Поскольку операция el_seg не модифицирует свой аргумент, нам больше не надо восстанав­ливать набор intset после выполнения итерации.

Хотя операция el_seg упрощает использование наборов intset, она неэффективна, особенно когда набор intset большой. Во-первых, имеем две структуры данных — сам набор intset и после­довательность. Во-вторых, в случае цикла поиска мы, возможно, выполняем лишнюю работу. В среднем в таком цикле не надо

128 Глава 6

Абстракция итерации

stblim == proc (s: intset) returns (iit) items: sequence [int] :== intset$el_seg(s) i: int :-= I sum: int := 0 while true do sum :== sum+ items [i]

except when bounds: return (srum) end i:=i+ I end end setsum

Рис. 6.2. Реализация процедуры setsum с использованием операции eLseg.

проверять все элементы множества, в котором организуется поиск. Например, если бы мы организовали в наборе intset поиск неко­торого отрицательного элемента, то могли остановиться тогда, когда встретили бы первый отрицательный элемент. Однако, мы должны обработать все элементы множества, чтобы построить последовательность.

Некоторой альтернативой для операции eLseg является опе­рация, которая просто выдает массив представления. Однако это решение является очень плохим, поскольку оно уничтожает аб­стракцию через спецификацию. Мы в действительности внесли операцию down и потеряли локальный контроль над представ­лением.

Требуется некоторый общий метод итерации, который удобен и эффективен и который сохраняет абстракцию через специфика­цию. Итератор обеспечивает эти требования. Он вызывается как 1 процедура, но вместо окончания с выдачей всех результатов цмеет 1 много результатов, которые выдает каждый раз по одному. По­рученные элементы могут использоваться в других модулях, ко­торые задают действия, выполняемые для каждого такого эле­мента. Использующий итератор модуль будет содержать некото­рую структуру организации цикла, например, такую:

for each для элемента результата i, выданного итератором А do выполнить над i некоторое действие

Каждый раз, когда итератор выдает некоторый элемент, над этим элементом выполняется тело цикла. Затем управление возвращается в итератор, так что он может выдавать следующий элемент.

Отметим разделение обязанностей в такой форме. Итератор ответствен за получение элемента, а модуль, содержащий цикл, определяет то действие, которое будет над ним выполняться. Итератор может использоваться в различных модулях, которые выполняют разные действия над элементами, и он может быть реализован различными способами, не оказывая влияния на эти модули.

Поскольку итератор выдает элементы по одному, исчезают проблемы пространства и времени, упоминавшиеся ранее. Нам не нужно строить потенциально большую структуру данных, кото­рая содержит такие элементы. Более того, если модуль выполняет некоторый цикл поиска, итератор может быть остановлен, как только будет найден интересующий его элемент.

Как упоминалось ранее, итераторы являются некоторой обоб­щенной формой методов итерации, имеющихся в большинстве языков программирования, В дополнение к некоторой форме цикла «while» языки программирования обычно имеют некоторую конструкцию «for» для итерации по целым числам. Такая итера­ция полезна при обработке массивов, которые индексируются, но она неудобна для таких неиндексируемых абстракций, как наборы itset.

(•» 6.1. Спецификация

'. ' Как и другие абстракции, итераторы должны быть определены через спецификации. Форма спецификации итерации аналогична форме для процедуры. Заголовок имеет такой вид:

iname = iter (...) yields (...) signals (...)

Здесь мы используем ключевое слово iter для обозначения абстракции итератора. Итератор может совсем не выдавать объек­тов на каждой итерации или выдать несколько объектов. Число и тип этих объектов описываются в предложении yields. (Если для каждого yields не выдается ни одного объекта, то предложение yields может быть опущено.) Итератор может не выдавать никаких результатов, когда он заканчивается нормально, но он может заканчиваться по исключительной ситуации с именем и резуль­татами, указанными в предложении signals. Например,

elements = iter (s: intset) yields (int) requires s не модифицируется в теле цикла.

effects Выдает элементы s в некотором произвольно?л порядке, причем каждый элемент только один раз.

Эта операция вполне правдоподобна для набора intset. Отме­тим, что операция elements не имеет исключительных ситуаций. Если ей будет задан в качестве аргумента пустой набор intset, она просто заканчивается, не выдавая ни одного элемента. Ха­рактерно, что использование итераторов исключает проблемы, связанные с заданием некоторых аргументов (таких, как пустой набор intset) для соответствующих процедур (таких, как про­цедура choose).

Мы требуем, чтобы набор s не модифицировался при использо­вании цикла. Подобные ограничения типичны для итераторов 5 Дисков Б,, Гатэг Дж.

130 Глава 6

Абстракция итерации

которые работают над такими изменяемыми объектами, как наборы intset, поскольку тогда станет неясно, что такое «элементы набора 8».'Эти ограничения будут еще обсуждаться в разд. 6.4.

6.2. Итераторы языка CLU

Итераторы могут быть реализованы в языке программирова­ния CLU при помощи модуля iter. Итераторы могут также исполь-аовать оператор for, имеющийся в языке CLU.

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