- •1.Введение
- •1.1.Декомпозиция и абстракция
- •1.2.Абстракция
- •1.2.1.Абстракция через параметризацию
- •1.2.2.Абстракция через спецификацию
- •1.2.3.Виды абстракций
- •3.Процедурная абстракция
- •3.2.Спецификации
- •3.3.Спецификации процедурных абстракций
- •3.4.Реализация процедур
- •3.5.Более обобщенные процедуры
- •3.6.Создание процедурных абстракций
- •3.7.Заключение
- •4.Абстракции данных
- •4.1.Спецификации для абстракций данных
- •4.2.1.Реализация на языке clu
- •4.2.2.Замечания по поводу операций up и down
- •4.3.Использование абстракций данных
- •4.4.Реализация полиномов
- •4.5.Пояснения для понимания реализаций
- •4.5.1.Функция абстракции
- •4.5.2.Инвариант представления
- •4.6.3.Сохранение инварианта представления
- •4.5.4.Изменяемые представления
- •4.6.Параметризованные абстракции данных
- •4.7.Списки
- •4.8.Упорядоченные списки
3.6.Создание процедурных абстракций
В данном разделе мы рассмотрим ряд проблем, возникающих при создании процедурных абстракций. Процедуры, как и другие виды абстракций, которые мы рассмотрим позднее, в процессе своего создания необходимо минимизировать. В них должны быть реализованы только необходимые "функцйТГ Это предоставляет разработчику большую свободу, позволяя ему создать более эффективную версию. Например, если приведенная на рис. 3.5 процедура sortпозволяет модифицировать аргумент, заданный массивом, то это уменьшает занимаемый ею объем памяти. Однако список значимых для пользователя подробностей такого рода должен быть ограничен.
К одному из таких пунктов, который обычно остается неопределенным, относится сам метод, используемый в конкретной реализации. Обычно пользователям предоставляется свобода в его выборе. (Впрочем, имеются исключения; например, процедура, работающая с числами, может быть ограничена хорошо известным числовым методом с тем, чтобы при округлении ее работа приводила к известным, четко определяемым погрешностям.) Также могут быть оставлены неопределенными некоторые выполняемые процедурой функции. В такой ситуации процедура становится недоопределенной.Это означает, что для определенных значений входных параметров на выходе вместо единственного правильного результата имеется набор допустимых результатов. Реализация может ограничивать этот набор только одним значением, однако он может быть любым из числа допустимых.
Процедура searchявляется недоопределенной, поскольку мы не указываем точно, какой индекс должен быть возвращен в том случае, если значение х встречается в массиве несколько раз. Как уже говорилось, две приведенные на рис. 3.4реализации процедуры searchотличаются именно в этом пункте. Однако каждая из них является корректной.
Процедура remove_dupls(рис. 3.3)также является недоопределенной, поскольку она не всегда сохраняет исходный порядок следования элементов в выходном массиве. Отсутствие такого требования может явиться причиной ошибки, поскольку пользователи могут быть заинтересованы в сохранении исходного порядка. Например, если входной массив отсортирован, то желательно сохранять исходный порядок. Важно отметить, что подобные ограничения зависят от нужд пользователей. _Каждая существенная для пользователей подробность должна быть оговорена в спецификации, а остальные оставлены неопределенными.
Недоопределенная абстракция может иметь детерминированную реализацию, т. е. такую, которая, будучи вызванной два раза с идентичными входными данными, выполняется одинаково. Обе реализации процедуры search,приведенные на рис. 3.4,являются детерминированными. (Недетерминированные реализации требуют использования своих собственных переменных, описанных в приложении А. В настоящей работе они не рассматриваются.)
Помимо требований к минимизации другим важным свойством процедур является обобщаемость, которая достигается путем использования параметров-переменных. Например, процедура, работающая с массивами произвольного размера, является более обобщенной, чем та, которая работает с массивами фиксированного размера. Аналогично процедура, работающая с массивами элементов произвольного типа, является более обобщенной, чем процедура, требующая, чтобы все элементы массива были целыми числами. Однако обобщение процедуры полезно только в том случае, если эффективность ее применения повышается. Это почти всегда так, когда речь идет о независимости от размеров, поскольку при этом небольшие изменения контекста применения процедуры (например, удваивание размера массива) требуют небольших модификаций в программе.
Другой важной характеристикой процедур является простота. Процедура должна обладать хорошо определенным и легко понимаемым назначением, независимым от контекста ее использования. Хорошим правилом может служить присваивание процедуре имени, описывающем ее назначение. Отсутствие такого дополнительного пояснения может породить сложности.
Наконец, процедурадолжнадействительно выполнять некоторыефункции.Процедуры создаются в процессе написания программы и служат цели упрощения и облегчения работы с ней, а также создания более ясной структуры программы. При этом программа становится более легко понимаемой. Однако существует опасность введения слишком большого числа процедур. Например, приведенная на рис. 3.6процедура mergeполезна потому, что ее назначение четко выражено, а также потому, что она позволяет нам отделить друг от друга задачи сортировки и слияния. Это делает процедуру merge_sortболее ясной и понятной. По всей видимости, дальнейшая декомпозиция является избыточной. Например, тело цикла в процедуре mergeможет быть выделено в процедуру, однако в этом случае ее назначение трудно определить.
Некоторые описанные ранее процедуры являются частичными, другие —общими. Подобная дихотомия поднимает вопрос о том, в каких случаях выгоднее определять частичную абстракцию. Частичные процедуры не так безопасны, как общие, поскольку они требуют от пользователя выполнения требований, заданных в предложении requires.Если эти требования не удовлетворены, то поведение процедуры становится неопределенным, что может привести к неверной работе программы. Например, при задании для процедуры searchнеупорядоченного массива она может возвратить неверный индекс. Эта ошибка может оставаться необнаруженной долгое время после возврата из процедуры search. Вследствие этого причина ошибки будет неясна, а объекты данных могут быть разрушены.
С другой стороны, частичные процедуры могут оказаться более эффективными в реализации, чем общие. Например, если процедура searchдолжна работать даже в том случае, если массив не был упорядочен, то ни одна из реализаций, приведенных на рис. 3.4,не будет правильной. В этом случае можно будет использовать только менее эффективный вариант, при котором анализируются все элементы массива.
При выборе между частичной и общей процедурами мы должны придерживаться определенных соглашений. С одной стороны, критерием должна являться эффективность. С другой —корректное выполнение с меньшим числом потенциальных ошибок. Каким образом осуществить такой выбор? Одним из важных факторов является ожидаемая область применения. Если процедура создается для общего пользования (например, доступна как часть библиотеки программ), то соображения безопасности играют существенную роль. В такой ситуации невозможно осуществить статистический анализ всех обращений к процедуре, позволяющий удостовериться в том, что необходимые требования удовлетворены. Следовательно, в этом случае полагаться на такой анализ неразумно.
Другой случай предполагает использование некоторых процедур в ограниченном контексте. Такой была ситуация, когда процедуры mergeи merge_sortвызывались только программойsort,использующей метод сортировки слиянием. В ограниченном контексте легко обеспечить выполнение необходимых требований. Например, два массива,, передаваемые процедуре merge, только что были отсортированы. Следовательно, мы имеем безопасное поведение, не жертвуя при этом эффективностью.
Наконец, необходимо наличие четкой и понятной спецификации. Написание хороших спецификаций является предметом обсуждения гл. 8.