- •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
156 Глава 7
то, вероятно, выгодно написать некоторую программу, которая переводит программы, написамые как здесь без ограничений, в программы, соответствующие данному компилятору. Такая программа сама может быть быстро написана на языке Паскаль.
В языке Паскаль требуется, щобы все идентификаторы были объявлены до того, как они используются. Для написания взаимно рекурсивных операций исильзуют^я формальные объявления. Формальное объявление задает заголовок некоторой операции, не задавая ее тела. Обвдц формат программы на языке Паскаль, соответствующий ограничениям на объявление, а затем на использование, всегда будет следующим: 1) реализация процедуры failure; 2) реализации всех абстрактных типов, которые будут использоваться в данной программе, включая и генераторы; 3) реализации всех операций, которые не являются частью не-когарого абстрактного тела, а также объявления констант, типов и переменных и формальныесбъявления; 4) тело данной программы.
Структура программы, соответствующей нашему примеру, была бы следующей: 1) реализация процедуры failure; 2) реализация бинарного дерева целых чисе.т intbintree; 3) реализация очереди queue [intbintree]; 4) реализация функции breadth.first. serch; 5) другие объявления и тцо программы.
76. Обзор главы
Подход к программированию, рассматриваемый бэтой книге, в большой степени независим от используемого языка программирования. Мы предполагаем, что, хотя выбор языка программирования для реализации программы окажет некоторое влияние на проектирование самой программы, это влияние не будет доминирующим. В частности, нет необходимости ограничивать типы йостракций, используемые при проектировании, теми типами, ко-тсрые непосредственно поддерживаются в данном языке программирования.
В нашем случае мы обратились к языку Паскаль — языку, который имеет относительно примитивные механизмы абстракций. Мы определили, как надо модифицировать абстракции процедур, итераций и данных, чтобы соответствовать языку Паскаль, и предложили соглашения по программированию, которые могут быть использованы для реализации абстракций данных, генераторов, полиморфных абстракций и обработки исключительных ситуаций па языке Паскаль. Наш подход 8 языку Паскаль может быть относительно просто адаптирован для других языков программирования (в течение нескольких лет мы читали курс, в котором использовался язык ПЛ/1). Мы рассмотрим такую адаптацию ч гл. 15.
Использование языка Паскаль
Дополнительная литература
Garland, Stephen J. 1986. Introduction to Computer Science with Applications in Pascal. Mass.: Addison-Wesley Publishing Co.
Упражнения
7.1. Реализуйте тип бинарного дерева целых чисел intbintree, заданный на рис. 7.12.
7.2. Специфицируйте некоторую абстракцию bintree [type], основанную на абстракции бинарного дерева целых чисел intbintree, заданную на рис. 7,12.
7.3. Реализуйте очередь целых чисел intqueue (рис. 7.1) так, чтобы доступ к первому и последнему элементам и их удаление могли быть сделаны за постоянное время. Надо ли изменять реализацию функции breadth-first-search для того, чтобы использовать преимущества этой лучшей реализации очереди целых чисел inttjueue?
7.4. Специфицируйте и реализуйте некоторую абстракцию обобщенного дерева, т. е. такого дерева, в котором может быть любое конечное число ветвей у каждого узла. (Указание: в своей реализации вы можете рассмотреть для каждого узла использование очереди его детей.)
7.5. Модифицируйте функцию breadth-first-search (рис. 7.13) так, чтобы она работала с обобщенным деревом, которое специфицировано в результате Вашего ответа на упражнение 7.4.
7.6. 'Специфицируйте и реализуйте некоторую абстракцию стека и используйте ее для реализации некоторой функции, которая выполняет поиск в ширину.
7.7. Специфицируйте и реализуйте некоторый генератор, который возвращает все листья в бинарном дереве целых чисел intbintree (рис. 7.12).
7.8. Что реализовано в программе, приведенной на рис. 7,14? (Предполагается, что сделаны спецификации, приведенные на рис, 7.1 и 7.10.)
program test: var i, sum: integer; q: intqueue; qi, qil: qelems; begin q := q-new;
for i := I to 3 do q.append (q, i); sum :== 0 qi := qelems_create (q); while not (qelems-done (qi)) do begin qil '.= qelems-create (q); while not (qelems-done (qil)) do
sum := sum + qelems-next (qil); qelems-destroy (qil); sum :== sum+ qelems_next (qi); end; qelems-destroy (qi); q .destroy (q); write Ln (sum) end {программа test)
Рис. 7.14. Программа на языке Паскаль»
8. Спецификации
В данной книге мы указывали важность.спецификаций на всех этапах разработки программного обеспеченияГПри этом подчеркивался тот факт, что корректное^прлменение спецификаций является ключевым моментом на пуйГ к эффективному программированию. Без спецификаций абстракции слишком сложны для того, чтобы оказаться действительно полезными^ В данной главе мы рассмотрим смысл спецификаций и некоторые критерии, которые необходимо учитывать при их написании. Мы также рассмотрим два основных назначения спецификаций.
8.1. Спецификации и необходимые наборы спецификаций
Назначение спецификации состоит в определении поведения абстракции. Пользователи полагаются н^.это поведение, а разработчики должны обеспечить его. Говорят,'' что-реализация, обеспечивающая описываемое поведение, удовлетворяет заданной спецификации. ' ,
Определим/значение спецификации через набор' >ёсех программных модулей, ей удовлетворяющих. Назовем это необходимым набором спецификаторов для данной спецификации. В качестве примера рассмотрим спецификацию
р = ргос (у: int) returns (х: int)
requires у j> 0 effects x >• у '.' ''•^'",'"•...!^,'^•'•.•''^
Она-удовлетворяется любой процедурой, с именем р, которая при вызове с аргументом, большим нуля, возвращает значение, большее этого аргумента. Члены необходимого набора включают в себя:
р = ргос (у: int) returns (int) return (у + 1) end р р = ргос (у: int) returns (int) return (у * 2) end р р = ргос (у: int) returns (int) return (у + 3) end р
Подобно каждой спецификации, данной спецификации удовлетво» ряет неограниченное число программ.
Спецификг.ции
Важно пешмняь, что спецификация, ее набор спецификаторов и некоторый член этого набора представляют собой весьма отличные друг от друга вещи, так же отличающиеся друг от друга, . как отличается программа от набора всех выполняемых ею возможных вычислений и работы этой программы с каким-то одним набором значений."^
.2. Некоторые критерии) к спецификациям
применяемые
3 Хорошие спецификации могут иметь различные формы, однако все они имеют схожие атрибуты. В данном разделе рассматриваются три важных атрибута — ограниченность, обобщенность и простота.
(" 8.2.1. Ограниченность
Lf Имеется большое различие между знанием о пригодности некоторых членов набора спецификаторов, и знанием того, что подходящими являются все члены. Это аналогично отличию знания о возможности работы программы с некоторыми наборами входных значений, от знания возможности ее работы со всеми входными значениями. Мы будем говорить о подобном отличии при обсуждении тестирования программ (см. гл. 9).'./Хорошая спецификация должна быть ограничена настолько, чтобы позволить выявить любую реализацию, не устраивающую пользователей данной абстракции. Это требование лежит в основе почти всех применений спецификаций. '/'
В общем случае обсуждение того, является ли спецификация достаточно ограниченной, предполагает рассмотрение задач, возлагаемых на членов набора спецификаторов,^меется ряд распространенных ошибок, которые почти всегда приводят к неадекватно ограниченным абстракциям^? К одной из таких ошибок' относитс^неполное задание требований в предложении requires.^/ Например, на рис. 8.1 приведены три спецификации итератора elems для портфеля целых чисел. (Портфель (bag) представляет собой обычный набор данных за тем исключением, что элементы в нем могут повторяться. Например, портфель bag [int] может содержать, число 3 дважды. Портфели иногда называют мульти-наборами.) В первой спецификации не дается ответа на вопрос, что произойдет, если b изменится в цикле, использующем elems. Она, следовательно, допускает реализации с резко отличающимся поведением. Например, приведет ли изменение b к изменению значений, возвращаемых elems?
160 Глава S
elems = iter (b: bag [t]) yields (e: t) effects Поочередно выдает каждый элемент из Ь.
elems == iter (b: bag [t]) yields (e: t)
requires b не изменяется в цикле, использующем elems. effects Поочередно выдает каждый элемент из b.
elems = iter (b: bag [t]) yields (e: t)
requires b не изменяется в цикле, использующем elems. effects Выдает все элементы из b в произвольном порядке. Каждый элемент из b выдается столько же раз, сколько раз он встречается в b.
Рнс. 8.1. Три спецификации для elems.
Одним из способов решения данной проблемы является введение требования о том, чтобы переменная b не изменялась внутри использующего elems цикла. Это требование задано во второй спецификации. Эта спецификация может бьпь, а может и не быть достаточно ограниченной, поскольку она не накладывает ограничений на порядок, в котором возвращаются элементы. Лучше, если спецификация будет определять порядок или содержать фразу «в произвольном порядке». Кроме того, в специфшка-ции не указано, что делается в том случае, если какой-нибудь элемент встречается в b более одного раза. В ней даже не говорится явно, что elems возвращает только те элементы, которые содержатся в b. В третьей спецификации рассмотренные недостатки устранены.
К другим ошибкам относится отсутствие полного списка исключительных ситуаций и описания .поведения программы для граничных случаев. Например, пусть операция string^indexs получает в качестве аргументов строки si и s2, и, если si является подстрокой в s2, возвращает индекс вхождения в s2 первого символа из si. Например: &iring,$'ndexs ("ab", "abc"») == 2
Спецпфикаиия, которая содержит только эту информацию, не будет достаючно ограниченной, поскольку она не объясняет, что произойдет в том случае, если si не является подстрокой в s2, пли если она встречается в s2 несколько раз, или если si или s2 пусты. Спецификация на рис. 8.2 является достаточно полной.
Сказанное позволяет сделать вывод о том, что написание достаточно эффективных спецификацнй должно производиться с большой осторожностью. Полезную роль здесь может сыграть язык формальных спецификаций (формальные спецификации рассматриваются в гл. 10).
iiidexs = proc (si, s2: string) returns (i: int)
effects Если si входит в s2 как подстрока, то i есть последний индекс вхождения si в s2. i равно 0, если si не входит в s2, и рапно 1, если si есть пустая строка. indexs ("be", "abcbc") = 2 indexs (" ", "a") = I
Рис, 8.2, Спецификация операции strin^indexs,
Спецификации
^8.2.2. Обобщенность
Хорошаяуспецификация должна быть достаточно обобщенной, позволяя сократить до минимума число исключаемых, однако приемлемых программ.^Важность критерия обобщенности может быть не столь очевидна, как критерия ограниченности./Жела-тельно не только сократить ограничения на число приемлемых реализаций, но и обеспечить возможность использования наиболее удачных (более эффективных или «элегантных») из нихУНапример, спецификация
sqrt = proc (sq: real, e: real) returns (root: real)
requires sq^O & e;> .001. effects 0 ^ (root * root —sq) ^ e.
ограничивает разработчика алгоритмами, которые отыскивают приближения, больщ^е или равные фактическому значению квадратного корня;, Эт^Чотраничение может привести к неоправданному снижению эффективности.
Желательно создавать спецификации максимально обобщенными. Это привело нас к «дефинитивному» стилю, который, и использован в данной книге.у Такая, спецификация яйй^Ь^ечис-ля^ свойства, которыми должны обладать спецификаторы. Альтернативой дефинитивной спецификации является операционная спецификация^Вместо описания свойств спецификаторов операционная спецификация приводит способ конструирования их. Например,
search = proc (a: array [int], х: int) returns (i: int) signais (not.in) effects Анализирует a[low], a[low+ 1],...,поочгредно ii возвращает индекс элемента, равного х. Сигнализирует not-in, если ни один из элементов не равен х.
есть операционная спецификация операции serach, в то время как
search = proc (a: array [int], х: int) returns (i: int) signals (not-in) effects Возвращает i, такое, что a[i] = х; х. Сигнализирует not_in, если такое i отсутствует.
есть дефинитивная спецификация. Первая спецификация объясняет, как реализовать операцию search, а вторая просто описывает свойства, которыми должны обладать входные и выходные данные. Дефинитивная спецификация не только является более короткой, но и предоставляет разработчику большую свободу, позволяя ему выбрать способ анализа элементов массива в удобном для него порядке.
"У Операционные спецификации имеют ряд преимуществ.1^Что более существенно./они &равнительно легко составляются опытными программистами—главным образом по причине того, что их составление очень сильно напоминает сам процесс программи-
(> Листов Б., Гатэг Дж.
