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

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

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