Safe Object-Oriented Software: The Verified Design-By-Contract Paradigm. David Crocker, Escher Technologies Ltd. Aldershot, United Kingdom (dcrocker@eschertech.com).
Объектно-ориентированный подход очень быстро завоевал популярность и на сегодняшний день используется практически во всех компаниях, занимающихся разработкой программного обеспечения. Такая популярность объясняется целым рядом преимуществ, которые предлагает ООП, основным из которых является переиспользование компонентов. Используя однажды написанные компоненты (классы, библиотеки, подсистемы) в нескольких проектах, компании могут в значительной степени оптимизировать процессы разработки и поддержки программного обеспечения.
В статье рассматривается использование подхода ООП в разработке программного обеспечения в областях, где к программному обеспечению предъявляются особенно жесткие требования к безопасности: авиация, космос, другие системы реального времени. Безопасность в таких проектах обеспечивается, помимо прочего, за счет использования статического анализа кода и качественного покрытия тестами. Абстракция, инкапсуляция и наследование — основные принципы ООП, — при правильном использовании позволяют повысить безопасность ПО уже на этапе проектирования, при этом не создают проблем для статического анализа. Однако при использовании полиморфизма и динамического связывания, когда неизвестно, какая из реализаций виртуальной функции будет в конечном счете вызвана, статический анализ кода, очевидно, невозможен. В качестве решения этой проблемы в работе предлагается подход Проектирования по контракту с верификацией.
Пусть S – это инструкция программы. Вызывающий код ожидает, что после выполнения S будет выполнено некоторое постусловие R (здесь R – это предикат от состояния программы, или математическое описание состояния, которое разработчик ожидает получить после выполнения S). Вообще говоря, это будет верно, только если перед вызовом S выполнено также некоторое предусловие P. Таким образом, чтобы проверить, что постусловие R будет выполнено после вызова S, необходимо проверить следующие утверждения:
В Проектировании по контракту S – это вызов метода. Для каждого метода документируются его предусловия и постусловия. Требования корректности выражаются в форме контракта между методом и кодом, его вызывающим:
В качестве примера рассмотрим систему, позволяющую отображать на экране различные объекты. Пусть все такие объекты реализуют интерфейс DisplayedElement, частью которого является метод draw(), по которому и происходит отрисовка. В качестве параметров этот метод принимает координаты и размер прямоугольника, в котором объект должен быть отображен. Рассмотрим, как подход Проектирование по контракту может быть применен к этому методу:
Также естественным будет требование того, что объекты на экране не пересекаются и не выходят за границы экрана. Это можно было бы описать в виде предусловий и постусловий для каждого из методов класса экрана Display, но Проектирование по контракту предлагает для таких условий более простой механизм — инварианты класса. Это глобальные условия, ограничения целостности, характеризующие данный класс. Вызывающий код может расчитывать, что это условие будет выполнено в любой момент времени.
При использовании механизмов динамического связывания в Проектировании по контракту выделяются понятия номинальной цели — виртуального метода интерфейса и фактической цели — конкретной реализации, которая определяется на этапе выполнения. Тогда основные принципы формулируются следующим образом:
Другими словами, п.п. 4-5 означают, что при наследовании реализации методов класса-наследника могут ослаблять предусловия, но должны иметь не менее сильные постусловия, чем переопределяемые методы класса-родителя (интерфейса).
Проектирование по контракту может быть реализовано множеством способов, от неформальных комментариев (документирование кода), оставляющих всю ответственность за разработчиком, до строгих описаний, которые могут проверяться автоматически. Так, существуют языки программирования со встроенной поддержкой контрактов (см. Eiffel, iContract). Но в этом случае ответственность за полноту описания контрактов и за их корректное использование при наследовании лежит на разработчике. Второй подход основан на документировании и расширенном статическом анализе кода. Наиболее продвинутым проектом, реализующим этот подход, является ESC/Java. Он представляет собой аннотированный Java, и позволяет находить действительно большое количество ошибок. Однако, анализатор ESC/Java не способен обнаружить все ошибки (или полное их отсутствие) или, в более общем смысле, что программа удовлетворяет указанным требованиям.
Кроме того, во всех этих случаях при использовани динамического связывания не гарантируется покрытие тестами всех путей выполнения кода. И сам подход, предполагающий разделение кода и аннотаций контрактов вызывает сомнения: разработчики скорее будут писать сперва код, и потом писать аннотации, что может привести к несогласованности, неполноте описаний, конфликтам и в конечном счете к сложноверифицируемому коду.
Ограничения неформальной и полуформальной реализации Проектирования по контракту могут быть устранены, когда все контракты являются формально верифицируемыми без каких-либо необоснованных предположений.
Проект «Escher» был задуман как набор инструментов, поддерживающих парадигму Проектирования по контракту с верификацией с практически 100% автоматизацией процесса верификации. Далее будут рассмотрены его основные положения.
Эти принципы были реализованы в инструменте Escher Tool, который был выпущен в виде коммерческого продукта Perfect Developer. Инструмент основан на нотации, разработанной для описания функциональных требований, спецификации (частью которой являются контракты) и коде реализации. Пример описания для PerfectDeveloper метода draw() из рассмотренного ранее примера:
Здесь ключевое слово pre предваряет предусловия, слово assert – постусловия. Слово ghost указывает на то, что для функции isDisplayedOn код генерироваться не будет, эта функция нужна для описания постусловия.
Как и большинство других аналогичных инструментов, PerfectDeveloper выполняет проверку типов. Условие верификации — это математическое утверждение, и для корректной программы все условия верификации — это теоремы. Таким образом, этот инструмент проверяет, за исключением небольшого числа документированных ограничений, что, если все условия верификации программы — верные теоремы, то программа корректно реализует спецификацию. В процессе генерации кода, инструмент вычисляет текущее состояние программы и, с его учетом, везде, где необходимо проверить условие верификации, генерирует теорему (здесь текущее состояние — это вычисленное состояние программы на данный момент, требуемое условие — это предусловие или постусловие метода, или инвариант класса):
текущее состояние => требуемое условие
Для обработки условий верификации Perfect Developer использует систему автоматического доказательства теорем, на основе модифицированной системы вывода Расева-Сикорского. Эта система работает с логикой первого порядка, и, хотя некоторые особенности нотации не могут быть выражены в рамках этой логики, такие случаи довольно редки, и могут быть обработаны с помощью простых преобразований термов.
Perfect Developer преобразует доказательства теорем (которые, как правило, человеку очень сложно разобрать) в специальный формат, разработанный как раз для того, чтобы быть доступным для человеческого понимания. Таким образом, все доказательства могут быть проверены вручную, если это требуется. Также Perfect Developer в качестве результатов, в том числе, предоставляет отчеты об ошибках.
Система предлагает также автоматическую генерацию кода. Таким образом, все предусловия и постусловия, условия верификации будут сгенерированы автоматически, что должно уменьшить количество человеческих ошибок.
Система была протестирована на трех проектах, генерировался код на С++. Сделана оценка, что при использовании системы Perfect Developer разработчик пишет от 1/3 до ¼ того объема кода, который он написал бы на чистом С++.
Объектно-ориентированный подход, очевидно, позволяет в значительной степени оптимизировать процесс разработки программного обеспечения. И сегодня, вместо того, чтобы отказываться от преимуществ ООП с тем, чтобы использовать старые средства статического анализа, общество должно скорее искать способы работы с новыми технологиями. Благодаря Perfect Developer разработчики систем, требовательных к безопасности, наконец, получили возможность использовать все преимущества подхода ООП в своих проектах.