Проектирование по контракту: построение надежного ПО
Базисные механизмы надежности
Необходимость уделить больше внимания семантическим свойствам классов становится особенно очевидной, если вспомнить что класс - это реализация АТД. Рассматриваемые до сих пор классы состояли из атрибутов и программ, реализующих функции спецификации АТД. Но АТД это не просто список операций: вспомните роль семантических свойств, выражаемых аксиомами и предусловиями. Они являются основой, проясняющей природу экземпляров данного типа. В классах мы - временно - потеряли этот семантический аспект концепции АТД. Необходимо вернуться назад, чтобы наше ПО было не только гибким и повторно используемым, но и корректным и устойчивым.
Утверждения и связанные с ними концепции, проясняемые в этой лекции, частично дают ответы. Не являясь полным доказательством, представленные ниже механизмы снабжают программиста основными средствами для формулирования и проверки аргументов корректности. Ключевой концепцией будет Проектирование по контракту (Design by Contract) - установление отношений между классом и его клиентами в виде формального соглашения, недвусмысленно устанавливающее права и обязанности сторон. Только через точное определение для каждого модуля требований и ответственности можно надеяться на достижение существенной степени доверия к большим программным системам.
При обзоре концепций мы впервые столкнемся с ключевой проблемой программной инженерии - как справиться с ошибками периода выполнения, возникающими при нарушении контракта. Этой теме - обработке исключительных ситуаций посвящена следующая лекция. Распределение ролей между двумя главами примерно отражает разницу между двумя компонентами надежности: корректностью и устойчивостью. Корректность - это возможность ПО выполнять свои задачи в соответствии со спецификациями, устойчивость - способность должным образом реагировать на ситуации, выходящие за пределы спецификации. Утверждения (эта лекция), как правило, покрывают корректность, а исключения (следующая лекция) - устойчивость.
Некоторые важные расширения основных идей проектирования по контракту должны ожидать введения наследования, полиморфизма и динамического связывания, что позволит нам перейти от контрактов к выдаче субподрядов.
Технические приемы, введенные в предыдущих лекциях, были направлены на создание надежного ПО. Дадим их краткий обзор - было бы бесполезно рассматривать более продвинутые концепции до приведения в порядок основных механизмов надежности. Первым и определяющим свойством объектной технологии является почти навязываемая структура программной системы - простая, модульная, расширяемая, - проще гарантирующая надежность, чем в случае "кривых" структур, возникающих при применении ранних методов разработки. В частности, усилия по ограничению межмодульного взаимодействия, сведения его к минимуму, были в центре дискуссии о модульности. Результатом стал запрет общих рисков, снижающих надежность, - отказ от глобальных переменных, механизм ограниченного взаимодействия модулей, отношения наследования и вложенности. Общее наблюдение: самый большой враг надежности (и качества ПО в целом) - это сложность. Создавая наши структуры настолько простыми, сколь это возможно, мы достигаем необходимого, но не достаточного условия, гарантирующего надежность. Прежнее обсуждение служит лишь верной отправной точкой в последующих систематических усилиях.
Заметьте, необходим, но также недостаточен, постоянный акцент на создание элегантного и читабельного ПО. Программные тексты не только пишутся, они еще читаются и переписываются по много раз. Ясность и простота нотации языковых конструкций - основа любого изощренного подхода к надежности.
Еще одно необходимое оружие - автоматическое управление памятью, в особенности сборка мусора. В лекции, посвященной этой теме, в деталях пояснено, почему для любой системы, оперирующей динамическими структурами данных, столь опасно опираться на управление этим процессом вручную. Сборка мусора не роскошь - это ключевой компонент ОО-среды, обеспечивающий надежность.
Тоже можно сказать об еще одном, сочетающемся с параметризацией механизме, - статической типизации. Без правил строгой статической типизации пришлось бы лишь надеяться на снисхождение многочисленных ошибок, возникающих в период выполнения.
Все эти механизмы дают необходимую основу для более полного взгляда на то, что следует предпринять для обеспечения устойчивости и корректности ПО.
О корректности ПО
Зададимся вопросом, что означает утверждение - программный элемент корректен? Наблюдения и рассуждения, отвечающие на это вопрос, могут показаться тривиальными. Но, как заметил один известный ученый, таковы все научные результаты, - они начинаются с обычных наблюдений и продолжаются путем простых рассуждений, но все это нужно делать упорно и настойчиво.
Предположим, некто пришел к вам с программой из 300 000 строк на С и спрашивает, корректна ли она? Если вы консультант, то взыщите высокую плату и ответьте - "нет". Вы, вероятно, окажетесь правы.
Для того чтобы можно было дать разумный ответ на подобный вопрос, одной программы недостаточно, необходима еще и ее спецификация, точно описывающая, что должна делать программа. Оператор
x:= y+1
сам по себе не является ни корректным, ни не корректным. Эти понятия приобретают смысл лишь по отношению к ожидаемому эффекту присваивания. Например, присваивание корректно по отношению к утверждению: "Переменные x и y имеют различные значения". Но не гарантируется его корректность по отношению к высказыванию: "переменная x отрицательна", поскольку результат присваивания зависит от значения y, которое может быть положительным.
Эти примеры иллюстрируют свойство, служащее отправной точкой в обсуждении проблемы корректности: программная система или ее элемент сами по себе ни корректны, ни не корректны. Корректность подразумевается лишь по отношению к некоторой спецификации. Строго говоря, мы и не будем обсуждать проблему корректности программных элементов, а лишь их согласованность (consistent) с заданной спецификацией. В наших обсуждениях мы будем продолжать использовать хорошо понимаемый термин "корректность", но всегда при этом помнить, что этот термин не применим к программному элементу, он имеет смысл лишь для пары - "программный элемент и его спецификация".
Свойство корректности ПО
Корректность - понятие относительное.
В этой лекции мы научимся выражать спецификации через утверждения (assertions), что поможет оценить корректность разработанного ПО. Но пойдем дальше и перевернем проблему, - разработка спецификации является первым, важнейшим шагом на пути, гарантирующем, что ПО действительно соответствует спецификации. Существенную выгоду можно получить, когда спецификации пишутся одновременно с написанием программы, а лучше, до ее написания. Среди следствий такого подхода можно отметить следующее.
- Разработка ПО корректного с самого начала, проектируемого так, чтобы быть корректным. Один из создателей структурного программирования Харлан Д. Миллс в семидесятые годы написал статью со знаменательным названием "Как писать корректные программы и знать это". Слово "знать" в данном контексте означает снабжать программу в момент ее написания аргументами, характеризующими корректность.
- Значительно лучшее понимание проблемы и достижение ее решения.
- Упрощение задачи создания программной документации. Как будет позже показано, утверждения будут играть важную роль в ОО-подходе к документации.
- Обеспечение основ для систематического тестирования и отладки.
Оставшаяся часть лекции посвящена исследованию этих вопросов. Одно предупреждение: языки программирования С, С++ и другие имеют оператор утверждения assert, динамически проверяющий истинность заданного утверждения в момент выполнения программы и останавливающий вычисление, если утверждение является ложным. Эта концепция, хотя и имеет отношение к предмету обсуждения, но является лишь малой частью использования утверждений в ОО-методе. Потому, если подобно многим разработчикам вы знакомы с этим оператором, не обобщайте ваше знание на всю картину, почти все концепции этой лекции, возможно, будут новыми.