- •4.9.1. Изменяемость
- •4.9.2. Классы операций
- •98 Глава 4
- •4.9.3. Полнота
- •100 Глава 4
- •4.9.5. Операции egual, similar и copy
- •102 Глава 4
- •104 Глава 4
- •5. Исключительные ситуации
- •108 Глава 5
- •110 Глава 5
- •6.2.1. Сигнализация об исключительных ситуациях
- •5.2.2. Обработка исключительных ситуаций
- •112 Глава 5
- •5.2.3. Предложение resignal
- •6.2.4. Необрабатываемые исключительные ситуации
- •114 Глава 5
- •6.2.3. Предложение resignal
- •5.2.4. Необрабатываемые исключительные ситуации
- •116 Глава 5
- •118 Глава 5
- •118 Глава 5
- •120 Глава 5
- •122 Глава 5
- •124 Глава 5
- •128 Глава 6
- •130 Глава 6
- •6.2.1. Реализация итераторов
- •6.2.2. Использование итераторов
- •132 Глава 6
- •134 Глава 6
- •136 Глава 6
- •138 Глава 6
- •140 Глава 7
- •142 Глава 7
- •7.2. Абстракция данных
- •144 Глава 7
- •146 Глава 7
- •148 Глава 7
- •150 Глава 7
- •7.4. Генераторы
- •162 Глава 7
- •154 Глава 7
- •156 Глава 7
- •162 Глава 8
- •164 Глава 8
- •166 Глава 8
- •168 Глава 8
- •170 Глава 9
- •172 Глава 9
- •174 Глава 9
- •176 Глава 9
- •9.1.3. Пример
- •178 Глава 9
- •180 Глава 9
- •182 Глава 9
- •184 Глава 9
- •186 Глава 9
- •188 Глава 9
- •190 Глава 9
- •192 Глава. 9
- •194 Глава 9
124 Глава 5
может быть пуст или не пуст), либо к тому факту, что такой объект не существует (например, существует наименьшее и наибольшее целое). Следовательно, операции типа должны использовать одинаковые имена исключительных ситуаций для одинаковых случаев. Например, операции для списков first и rest сигнализируют об исключительной ситуации empty, если список, заданный в качестве аргумента, пуст, а операции для целых чисел, результатом которых является слишком большое число, сигнализируют об исключительной ситуации overflow.
Исключительные ситуации предоставляют информацию, которая обычно может быть получена прямым обращением к операциям. Например, о факте пустого списка можно узнать, либо обратившись к операции first и получив ее сигнал, либо обратившись к операции empty. Такая избыточность не необходима. Например, возможен тип, в котором узнать, пуст объект или не пуст, можно только через исключительную ситуацию.
В наших примерах реализаций мы не стремились избегать возникновения исключительных ситуаций; наоборот, мы использовали их для управления работой программ. Исключительные ситуации могут улучшить производительность за счет уменьшения количества вызовов процедур. Например, реализация процедуры choose (рис. 5.4) использует исключительную ситуацию bounds для того, чтобы избежать обращения к операции size для массивов.
5.5. Заключение
В этой главе мы обобщили процедуры, введя исключительные ситуации. Исключительные ситуации необходимы для создания устойчивых программ, так как предоставляют способ реагировать на ошибки. Если аргумент не тот, который ожидается, процедура может оповестить обратившегося к ней об этом факте, вместо того чтобы просто выйти на сбой. Так как это оповещение отличается от обычного случая, обратившийся к процедуре не может их перепутать.
Исключительные ситуации вводятся при разработке процедур. Большинство процедур должно быть задано на всей области определения; исключительные ситуации используются для обработки ситуаций, в которых «обычная» работа программы не может быть осуществлена. Частичные процедуры используются только по соображениям эффективности или когда процедура используется в ограниченном контексте, о котором точно известно, что все обращения имеют корректные аргументы. В любом случае при реализации процедуры полезно практиковать защитное программирование, сигнализируя об исключительной ситуации failure там, где только возможно,' при значениях аргументов, не принадлежащих принятому подмножеству области определения.
Исключительные ситуации
При реализации процедуры программист должен гарантировать, что она во всех ситуациях закончится в соответствии со спецификацией. Следует сигнализировать только об исключительных ситуациях, указанных в спецификации. Сигнализировать следует, кроме того, только по правильным причинам. При создании реализации разумно использовать исключительные ситуации вызываемых процедур и в зависимости от обстоятельств распространять или маскировать их.
Дополнительная литература
Goodenough, John В., 1975. Exception handling: issues and proposed notation. 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 и последовательность. Во-вторых, в случае цикла поиска мы, возможно, выполняем лишнюю работу. В среднем в таком цикле не надо
