- •10. Написание формальных спецификаций
- •Глава II
- •12. Предварительные замечания о процессе разработки программ
- •12.1. Жизненный цикл математического обеспечения
- •12. Предварительные замечания о процессе разработки программ
- •12.1. Жизненный цикл математического обеспечения
- •12.2. Анализ требований
- •12.3. Пример задачи
- •13.1. Обзор процесса проектирования
- •13.2.1. Вводный раздел
- •13.2.2. Разделы абстракций
- •13.8. Абстракция строки
- •13.9. Обзор и обсуждение
- •14. Этап перехода от проектирования к реализации
- •14.1. Оценка проекта
- •14.1.1. Корректность и эффективность
- •14.1.2. Структура
- •15.2. Выбор подхода
10. Написание формальных спецификаций
Большинство спецификаций в данной книге написаны квази-зрмально, без использования строгих правил. Фактически они Представляют собой сильно стилизованные комментарии. Мы 1выбрали неформальные спецификации, исходя из следующих воображений.
1. Хотя формальные спецификации и принадлежат к многообещающим областям в исследованиях методов программирования, выгода от их применения при разработке различного программного обеспечения еще должна быть продемонстрирована.
2. Овладение техникой написания формальных спецификаций требует времени. Мы не думаем, что в течение односеместро-вого курса студенты могут освоить формальные спецификации вместе с другими материалами из этой книги.
3. Написание формальных спецификаций затруднено также и тем, что для этого требуется некоторая машинная поддержка. По крайней мере необходимы средства проверки синтаксиса и типа. Большинство читателей данной книги такими средствами не обладают.
В данной главе мы рассмотрим язык, используемый для написания формальных спецификаций. Мы убеждены, что студенты должны быть знакомы с формальными спецификациями хотя бы на уровне книги и что это знание окажет большую помощь при написании неформальных спецификаций.
Вспомните, что наши неформальные спецификации абстракций данных включают раздел, в котором дается интуитивное описание определяемого типа. Обычно это описание ссылается на некоторую область, с которой пользователи предположительно знакомы. Например, мы определяем набор intset в терминах математических наборов и poly в терминах полиномов над полем целых чисел. Проблема, связанная с использованием неформальных спецификаций, заключается в том, что такие дополнительные области никогда не определяются четко. Если читатели имеют интуитивное понимание материала из затронутой области и это их представление соответствует рассматриваемому, то они будут в состоянии понять спецификации, в противном случае — нет. Что еще хуже, и это говорилось в гл. 8 нет способа узнать, интерпретирует ли
Дополнительная литература
Bj0rner, Dines, and Cliff B. Jones, 1982. Formal Specification & Software] Development. Englewood Cliffs, N. J.: Prentice-Hall International.
Guttag, John V,, James J. Homing, and Jeannette M. Wing, 1985. Larch: in five easy pieces. Technical report 5, Digital Equipment Corporation Systems' research Center.
I
Краткий обзор процесса верификации программ
Упражнения
10.1. Рассмотрим спецификацию абстракции bag, приведенную на рис. 10.6. Покажите, что каждый свободный по переменным терм с оператором на самом верхнем уровне IS. IN равен терму, в котором нет ни одной функции, определенной в области INBAG.
10.2. Добавьте к спецификации на рис. 10.6 подходящий partitioned by. 10.3. Добавьте к спецификации на рис. 10.6 функцию COUNT. Она должна иметь структуру: COUNT: INTBAG, INT -^ INT и возвращать число вхождений в bag целых чисел.
10.4. Модифицируйте спецификацию на рис. 10.6 таким образом, чтобы операция DELETE удаляла все вхождения удаленного элемента.
10.5. Напишите полную спецификацию абстракции table, которая отобра-экает строки в целые числа. Она должна иметь операции для создания пустой таблицы, добавления в таблицу строки и связанного^ этой строкой целого числа, удаления из таблицы строки и соответствующего целого числа, поиска строки в таблице и операцию, определяющую число отображений в таблице. Таблицы должны быть изменяемыми. (Указание: используйте сорт STRING-TABLE, рассмотренный в разд. 10.1.1 и 10.1.2.)
10.6. Напишите полную спецификацию типа, представляющего ограниченный стек. Она должна иметь процедуры со следующими заголовками:
new == proc (i: int) returns (s: stack)
signals (non_ positive-Size)
push = proc (s: stack, i: int) signals (overflow, duplicate) pep = proc (s: stack) returns (i: int) signals (empty)
Целое число, передаваемое процедуре ne\v, есть максимальный размер стека size. Если оно не больше нуля, то процедура new сигнализирует о возникновении исключительной ситуации non_ positive- Size. Процедура push выдает сигнал о переполнении overflow, если запись в стек передаваемого ей целого числа вызывает ув5личен1:е стека до недопустимых размеров. Она выдает сигнал duplicate, если передаваемое ей целое число уже имеется в стеке. Процедура pop возвращает последний размещенный в стеке элемент и удаляет его из стека. (Указание: воспользуйтесь сортом INTST-ACK, рассмотренным в разд. 10.1.5, модифицировав его соответствующим образом.)
10.7. Напишите спецификацию на естественном языке, которая содержит информацию о всех ваших ответах на упражнения 5 или 6. Не кажется ли вам, что подобная спецификация была бы такой же полной, если бы вы не написали сначала формальную спецификацию?
10.8. Напишите формальную спецификацию для процедуры poly (рис. 4.3). В процессе составления этой спецификации вам понадобится трейт с соответствующими функциями для связанного с ним сорта.
10.9. Напишиге спецификацию для очереди, организованной по принципу д^че? «первый поступивший удаляется первым». В процессе составления этой специфи- ДЖЙ; кгцни вам понадобится трейт с соответствующими функциями для связанного г ним сорта.
Эта глава представляет собой краткий обзор относительно сложной темы. Мы попытаемся дать представление о процессе верификации без углубления в мириады подробностей, предполагающих составление строгих доказательств, относящихся к свойствам программы. Мы убеждены, что понимание того, что подразумевается под формальной верификацией программ, является ценным качеством для их неформального анализа; при этом самым ценным, что можно было бы вынести из данной главы, является понимание сущности основных методов, используемых в формальных рассуждениях. Важна структура" Правил доказа-Тедьств, а также сами доказательства. Подробности несущественны.
Верификация программы предполагает анализ ее текста. Это отличает верификацию от тестирования, при котором всегда производится наблюдение за вычислениями. В процессе верификации мы анализируем текст программы и делаем выводы по Поводу описываемого программой набора вычислений. Мы часто выдаемся к этому набору для подтверждения правильности наших рассуждений, однако никогда не создаем этот набор или его часть.-Начнем с рассмотрения базовой задачи верификации и анализа текста программы, не содержащего ветвлений. Затем рассмотрим операторыifи циклыwhile, коснемся процесса доказательства соответствия процедур своим спецификациям и рассмотрим программы, вызывающие процедуры. Наконец, проведем анализ программ, содержащих кластеры.
Для большей иллюстративности примеров мы сознательно будем избегать точных определений. Более того, мы не будем рассматривать все выражения и операторы языкаCLU, а будем составлять программы таким образом, чтобы в них использовались только уже рассмотренные конструкции. Также не будем рассматривать алиасы, рекурсивные процедуры, итераторы и исключительные ситуации.
11.1. Анализ программ, не содержащих ветвлений
Для доказательств мы будем использовать формулы полной корректности.Каждая такая формула включает в себя два предиката —предусловиеипостусловие,а также сегмент программы. Областью предикатов является набор всех возможных состояний вычислений. Формула полной корректности имеет вид
Р^ ...S,} Q
В ней утверждается, что если состояние процесса вычислений перед выполнением операторов 8152...S„ удовлетворяет предусловию Р, то мы хотим, чтобы 5182 •••S„ выполнились, а состояние, предшествующее последующим вычислениям, удовлетворяло бы условию Q.Разумеется, если в начальном состоянии предусловие ложно, то вся формула истинна. Это аналогично правилу логики, утверждающему, что
(FALSE^ Р) =TRUE
Здесь мы придерживаемся соглашения, по которому идентификаторы, не использующиеся в программах (подобные TRUEиFALSE), обозначаются заглавными буквами. Отметим, что пара предикатов в этих формулах очень напоминает тело спецификации процедуры. Предусловие сходно с предложениемrequires, а постусловие —с предложениемeffects. Рассмотрим пример
TRUE{х:=о
у:=1} у>х
Как мы можем убедиться в истинности этой формулы? То есть откуда мы знаем, что данный фрагмент программы завершился и после этого значение у больше значения х? Для начала мы отметим, что выполняются в точности два оператора. После выполнения второго оператора значение у есть 1,а значение х соответствует тому, каким оно стало после выполнения первого оператора, т. е. 0.Завершая наши рассуждения, отметим, что 1 > 0. В приведенной аргументации мы рассуждали об 1)управляющем потоке сквозь программу, 2)значении оператора присваивания и 3)значении операции >для целых чисел.
Основной прием, который мы будем использовать в наших рассуждениях, касающихся программ, можно сформулировать следую1Ц);?.1 образом:
1. Локализация всех путей между двумя предикатами.
2.Для каждого пути выполняется проход назад от конечного предиката до обнаружения производного предикатаR, который
ий обзор процесса верификации программ
должен быть истинен по отношению к первому оператору в данном пути, если при этом истинен конечный предикат.
3.Информация о типах значений переменных, входящих предикаты, используется для установления того факта, что пред-•словие для каждого пути включает производный предикат R. ^ Только что рассмотренное простое доказательство может быть 1переформулировано более аккуратно. Проход назад через второе Присваивание с исходной посылкой у >х упрощает данный 1'фрагмент программы до следующего:
itrue{x:=o}i>x
а дальнейший проход назад через оставшееся присваивание с 1 >х в качестве исходной посылки дает
.TRUE =» 1 >О
которое упрощается доTRUE, следуя правилам логики для целых чисел. В построении этого доказательства для продвижения предикатов назад через операторы присваивания мы воспользовались следующим правилом присваивания: Пусть Р есть любой предикат, а е есть любое выражение, свободное от побочных эффектов: если Р истинен после присвоения хs =е, то Р с е, подставленным во все свободные вхождения х (т. е. вхождения, не связанные квантором), перед этим присваиванием должен быть истинен. Это означает, ^ что предикат Р, в котором е подставлено во все свободные ' вхождения х, есть самое слабое_ предисловие, обеспечивающее ^ истинность Р после присваивания х :==е. ' "• На первый взгляд выполнение прохода назад по программе может показаться несколько странным. Преимущество такого способа заключается в том, что таким образом мы последовательно достигаем поставленной цели: на каждом шаге, зная, что мы хотим удостовериться в правильном выполнении каждого фрагмента программы, мы вычисляем то, что должно быть истинным по отношению к данному шагу. По достижении начала программы мы получаем самое слабое условие, выполнения которого достаточно для подтверждения истинности постусловия («самое слабое» в том смысле, что его включает в себя любое другое достаточное предусловие). Мы завершаем доказательство, показывая, что данное предусловие включает в себя самое слабое предусловие.
Если мы хотим двигаться вперед, то необходимо начать с того, что предполагается истинным в начале выполнения программы, и вычислить то, что должно быть истинным по завершении данной части. Достигнув, таким образом, конца программы, мы будем иметь рамое сильное постусловие, вытекающее из известного нам предусловия («самое-сильное» в том смысле, что оно предполагает выполнение любых других значимых постусловий). В этом случай
228 Глава II
мы завершим ддк^атед^тво, если покажем, что-то самое строгоепocтуcлoвиe^включaeт\вГceбя^имeю^eecl,Jlocтуcл^)Jвиe.''Boзникa-ющая"прй' таком подходе проблема заключается в том, что при просмотре программы вперед у нас отсутствует метод последовательного исключения незначимой информации. Это приводит к накоплению множества утверждений, являющихся истинными, но не имеющих никакого отношения к доказательству.
Как мы уже видели, два предиката в формуле полной корректности аналогичны предложениямrequiresиeffectsв спецификациях процедур. Однако между ними имеется ^щественное^раз-личие. В спецификации процедуры предикатltfectsимеет дело ^Гд5умя состоя ни ями: до и после обращения. Когда мы ссылаемся к значению формального параметра, то рассматриваем этот параметр (порой неявно) как имеющий префикс пре- или пост-. В формуле полной корректности каждый предикат относится только к одному состоянию. Это порождает небольшую проблему, возникающую при необходимости установления связи между начальными и конечными значениями.
В качестве примера рассмотрим формулу полной корректности, утверждающую, что оператор х :==х+1приводит к увеличению х на 1.Необходимо, чтобы постусловие ссылалось как к начальному, так и к конечному значению х. Для этого мы воспользуемся приемом, введя в предусловиеновую переменнуюх() и считая, что она имеет то же значение, что и х. Она является «новой» в том смысле, что еще ни разу не встречалась ни в программе, ни в пост-или предусловии. Для подтверждения того, что х :== х + 1, запишем
Хо == х )х :== х+Ц х > Хо
Для проверки данной формулы мы продвинем предикат х >х через оператор присваивания: -
(Хо ==х) ^ (х+1 >Хо)
Подставляя в правую часть Хд вместо х, получаем Хо + 1 >Хц, что, считая, что х имеет тип целое, упрощается доTRUE.
11.2. Анализ программ с ветвлениями
До сих пор мы рассматривали программы без ветвлений. Теперь рассмотрим программу g двумя ветвями, например
if х == I then у :== 0 else у :== х end
В ней имеется один путь для предложения thenи один —для предложенияelse. Рассматривая предложениеthen, мы можем считать, что предикат в условии истинен, а предикат дляelse — ложен. В общем случае при разборе программ с операторамиif
аткий обзор процесса верификации программ
_используем следующее правило анализа условного выражения: !£сли вычислениеbне имеет побочных эффектов, то доказательство
р \it b then si else s2\ Q
эквивалентно доказательству пары Р &b {811и Р &~b {52}Q. 1Более кратко правило вывода может быть записано следующим 1образом:
^без-побочных-эффектов (b), Р & b \&\\ Q, Р & ^b \s2\ Q "—Р\Нbthensielse s2\Q—~~
В этом представлении находящиеся поверх горизонтальной черты и разделенные запятой предложения называются гипотезой. Утверждение, расположенное под чертой, называется заключением. Это правило может быть рассмотрено как утверждение, говорящее о том, что истинности всех гипотез достаточно для доказательства истинности заключения.
Правило анализа условных выражений является первым в ряду правил, которые мы будем использовать для сведения доказательства сложных формул общей корректности к набору более простых формул или условий истинности. Например, для доказательства
(х = 1) 1 (х-0)
{if х = I then у ;= 0 else х := у end) (у =0)
нам необходимо доказать три условия истинности:
1)без-побочных-эффектов (х = 1)
2) ((х = 1)1 (х-0)) &(x = 1) iy :- 0\ у = О
3)((х = 1) 1(х =0))&~ (х-1){у :=xlу ==О Истинность второго и третьего условий может быть заказана при помощи правила о присваивании. Истинность третьего условия истинности следует из того факта, что единственная процедура, вызываемая данным выражением, естьmt$equal, а она ничего не модифицирует.
Рассмотренный выше способ анализа условных конструкций приложим к любому сегменту программы с относительно небольшим числом ветвлений. К сожалению, как это уже отмечалось в гл. 9,степень ветвления большинства программ довольно велика. Рассмотрим, например
х>0 & у == О
{vAiile —(у == х) do у := у + I end} у- х
В данном фрагменте программы имеется бесконечное количество путей, по одному для каждого возможного значения х. Если
232