Это последняя статья из цикла статей о теоретических аспектах проектирования по контракту, в которой рассматриваются уровни мониторинга утверждений во время выполнения, а также роль контрактов в создании документации.
Мониторинг утверждений в период выполнения
Как уже говорилось ранее, поведение системы при нарушении утверждений во время выполнения полностью зависит от разработчика. Механизмы проектирования по контракту предоставляют возможности управлять этим поведением путем задания соответствующих параметров компиляции, тем самым включая и отключая проверки времени выполнения по своему усмотрению.
Перечень подобных настроек определяется используемым языком или библиотекой, а также количеством поддерживаемых утверждений (как уже говорилось ранее, не все реализации принципов проектирования по контракту поддерживают инварианты циклов). Если рассматривать полный перечень утверждений, изначально предложенный Бертраном Мейером в [Meyer2005], то можно выделить следующие уровни мониторинга:
no – во время выполнения нет проверок никаких утверждений. В этом случае утверждения играют роль комментариев;
require – проверка только предусловий на входе методов;
ensure – проверка постусловий на выходе методов;
invariant – проверка выполнимости инвариантов на входе и выходе всех экспортируемых методов;
loop – проверка выполнимости инвариантов циклов;
check – проверка инструкций утверждений;
all – выполнение всех проверок (в языке Eiffel этот уровень мониторинга эквивалентен уровню check);
Каждый последующий уровень автоматически влечет за собой выполнение всех предыдущих. Именно поэтому check и all в языке Eiffel определяют одинаковые уровни мониторинга.
Результат нарушения утверждения в период выполнения также может изменяться в зависимости от параметров компиляции и может различаться в зависимости от реализации. Так, в языке Eiffel нарушение утверждения в период выполнения всегда приводит к генерации исключения, а в библиотеке Code Contracts разработчик может выбрать требуемое поведение; это может быть либо генерация исключения, либо нарушение стандартного утверждения (assertion), что приведет остановке выполнения программы и выдаче пользователю стандартного диалогового окна.
Оптимальный уровень утверждений
Подобная гибкость автоматически приводит к еще одному вопросу: какой уровень мониторинга утверждений во время выполнения является оптимальным? Ответ на него выбирается как компромисс между уровнем доверия к качеству кода и последствиями необнаруженных ошибок в период выполнения.
Существует два простых крайних случая. Во время отладки системы мониторинг утверждений должен быть включен на максимальном уровне, а для систем с высокой степенью доверия к коду, критичных ко времени выполнения мониторинг утверждений может быть отключен полностью. Если первый случай особых вопросов не вызывает, то второй совет звучит весьма неоднозначно. Хотя в настоящее время широко развиваются системы статистического анализа кода, которые благодаря механизму утверждений способны выявить значительное количество ошибок, формальных доказательств корректности кода все еще не существует, поэтому говорить о “полном” доверии к коду вряд ли возможно. Здесь очень уместно высказывания Тони Хоара:
Абсурдно выполнять проверку в период отладки, когда не требуется доверие к получаемым результатам, и отключать ее в рабочем состоянии, когда ошибочный результат может стоить дорого или вообще катастрофичен. Что бы вы подумали о любителе плавания, который надевает спас-жилет во время тренировок на берегу и снимает его, бросаясь в море? [Hoare1973]
Оптимальный уровень мониторинга определяется требованиями конкретного приложения и замерами производительности приложения с включенными и отключенными проверками. Можно говорить о том, что для большинства готовых систем (речь не идет о периоде отладки, во время которого мониторинг должен быть включен на максимальный уровень) вполне достаточным является проверка предусловий. Эта проверка обходится намного дешевле, чем проверка постусловий (которые могут содержать сложную логику или ссылаться на предыдущее значение переменных) или инвариантов (которые проверяются дважды для каждой экспортируемой функции: один раз перед ее выполнением, а второй раз – перед завершением) и дает гарантии того, что код будет выполняться внутри области его применения.
Проверка предусловий может быть стартовой точкой для вашего приложения, но только после тщательного анализа и профилирования можно говорить о влиянии утверждений на выполнение вашей программы и об оптимальном уровне проверки утверждений во время выполнения.
Контракты и документация
Помимо явных преимуществ использования контрактов для создания надежного и расширяемого программного обеспечения, утверждения обладают еще одним важным преимуществом – самодокументированием.
Как уже говорилось ранее, обычно, если спецификация и существует, то является отдельным документом, что является нарушением принципа “Нет избыточности” [Meyer2005] или принципа DRY (Don’t Repeat Yourself) [Hunt2002]. Подобное дублирование информации пагубно по той простой причине, что проблемы рассогласования дублируемой информации является лишь вопросом времени. Как бы ни старалась команда разработчиков (или даже выделенный человек) поддерживать информацию в различных источниках в согласованном состоянии, рано или поздно наступит момент, когда рассогласование информации все же произойдет.
Поскольку проектирование по контракту предполагает содержание элементов спецификации в самом исходном коде, мы получаем единый источник информации о программной системе, вероятность рассогласования которого минимальна.
В случае проектирования по контракту, предусловия, постусловия и инварианты классов обеспечивают потенциальных клиентов модуля всей необходимой информацией о предполагаемых службах, выраженных в соответствующей и точной форме. “Никакое количество описательной документации не может заменить множества аккуратно выраженных утверждений, являющихся частью самого ПО”.
Выводы
Формализация отношений играет важную роль при взаимодействии двух программных элементов: клиента и поставщика услуги. Клиент точно знает, что нужно сделать, чтобы вызов метода поставщика был легитимен, а поставщик точно знает, при каких условиях он должен выполнить свою задачу и не задумывается о том, что же ему следует предпринять, если эти условия не будут выполнены. Нарушение контракта – это отклонение реализации от заданной спецификации и является проявлением ошибки либо на стороне клиента (при нарушении предусловия), либо на стороне поставщика (при нарушении постусловия или инварианта). Такая формальная спецификация взаимоотношений в значительной степени упрощает создание систем, корректных с самого начала, в противоположность подходу, пытающемуся добиться корректности в процессе отладки.
Литература
- [Meyer2005] Мейер Б. Объектно-ориентированное конструирование программных систем. М.: Русская редакция, 2005
- [Meyer2009] Meyer B. Touch of Class. Learning to Program Well with Objects and Contracts. London: 2009
- [Hoare1981] C.A.R. Hoare: The Emperor’s Old Clothes (1980 Turing Award lecture), in Communications of the ACM, vol. 24, no. 2, February 1981, pages 75-83.
- [Hunt2000] Хант Э., Томас Д. Программист-прагматик. Путь от подмастерья к мастеру. М.: Лори, 2007
- [McConnell] Макконнелл С. Совершенный код. 2-е издание. СПб.: Питер, 2005
- [Howard] Ховард М., Лебланк Д. Защищенный код. 2-е издание. М.: Русская редакция, 2005
- [Maguire] Maguire, Steve. Writing Solid Code. Redmond, WA: Microsoft Press, 1993
- [Coplien1992] Джеймс Коплиен. Программирование на С++. Питер, 2005
- [Hoare1973] C.A.R. Hoare: Hints on Programming Language Design, Stanford University Artificial Intelligence