Ппк и параметры-константы

Во многих случаях постусловие будет использовать параметры, переданные в метод, для проверки правильности поведения. Но если подпрограмме разрешено изменять переданный параметр, то у вас есть возможность обойти условия контракта. В отличие от языка Java, язык Eiffel не позволяет подобных действий. В данном случае для указания наших намерений, сводящихся к неизменяемости параметра в пределах метода, используется ключевое слово final (из языка Java). Это не является защитой от дурака – подклассы не имеют ограничений при повторном определении параметра как не являющегося окончательным. В качестве альтернативы можно использовать синтаксис variable@pre (принятый в iContract), чтобы получить исходное значение переменной, существовавшее на момент входа в метод.

Следовательно, в контракте между подпрограммой и любой потенциально вызывающей ее программой может быть записано следующее:

Если вызывающая программа выполняет все предусловия подпрограммы, то подпрограмма гарантирует, что по завершении ее работы все постусловия и инварианты будут истинными.

Если одна из сторон нарушает условия контракта, то применяется предварительно согласованная мера, например, возбуждается исключение или происходит завершение работы программы. Что бы ни происходило, вы не ошибетесь, утверждая, что нарушение условий контракта есть ошибка. Это происходит далеко не всегда, и поэтому предусловия не должны использоваться для осуществления таких процедур, как проверка правильности данных, вводимых пользователем.

Подсказка 31: Проектируйте в соответствии с контрактами

В разделе «Ортогональность» рекомендуется создавать «скромные» программы. В данном случае упор делается на «ленивую» программу: проявите строгость в том, что вы принимаете до начала работы, и обещайте как можно меньше взамен. Следует помнить, что если в контракте указано, что вы принимаете все условия, а взамен обещаете весь мир, то вам придется написать… ну очень большую программу!

Наследование и полиморфизм являются краеугольными камнями объектно-ориентированных языков программирования и представляют собой область, в которой принцип программирования по контракту может проявиться особенно ярко. Предположим, что вы используете наследование при создании связи типа «это-схоже-с-тем», где один класс «схож-с-тем» (другим) классом. Вероятно, вы действуете в соответствии с принципом замещения, изложенным в книге Liskov Substitution Principle [Lis88]:

Использование подклассов должно осуществляться через интерфейс базового класса, но при этом пользователь не обязан знать, в чем состоит различие между ними.

Другими словами, вы хотите убедиться в том, что вновь созданный подтип действительно схож-с тем (базовым) типом – что он поддерживает те же самые методы и эти методы имеют тот же смысл. Этого можно добиться при помощи контрактов. Контракт необходимо определить единожды (в базовом классе) с тем, чтобы он применялся к вновь создаваемым подклассам автоматически. Подкласс может (необязательно) использовать более широкий диапазон входных значений или же предоставлять более жесткие гарантии. Но, по крайней мере, подкласс должен использовать тот же интервал и предоставлять те же гарантии, что и родительский класс.

Рассмотрим базовый класс Java, именуемый java.awt.Component. Вы можете обрабатывать любой визуальный элемент в AWT или Swing как тип Component и не знать, чем является подкласс в действительности – кнопкой, подложкой, меню или чем-то другим. Каждый отдельный элемент может предоставлять дополнительные, специфические функциональные возможности, но, по крайней мере, он должен предоставлять базовые средства, определенные типом Component. Однако ничто не может помешать вам создать для типа Component подтип, который предоставляет методы с правильными названиями, приводящие к неправильным результатам. Вы легко можете создать метод paint, который ничего не закрашивает, или же метод setFont, который не устанавливает шрифт. AWT не обладает контрактами, которые способны обнаружить факт нарушения вами соглашения.

При отсутствии контракта все, на что способен компилятор, – это дать гарантию того, что подкласс соответствует определенной сигнатуре метода. Но если мы составим контракт для базового класса, то можем гарантировать, что любой будущий подкласс не сможет изменять значения наших методов. Например, вы составляете контракт для метода setFont (подобный приведенному ниже), гарантирующий, что вы получите именно тот шрифт, который установили:

/**

* @pre f != null

* @post getFont() == f

*/

public void setFont(final Font f) {

//…

Реализация принципа ППК

Самая большая польза от использования принципа ПИК состоит в том, что он ставит вопросы требований и гарантий во главу угла. В период работы над проектом простое перечисление факторов – каков диапазон входных значений, каковы граничные условия, что можно ожидать от работы подпрограммы (или, что важнее, чего от нее ожидать нельзя), – является громадным шагом вперед в написании лучших программ. Не обозначив эти позиции, вы скатываетесь к программированию в расчете на совпадение (см. раздел Программирование в расчете на стечение обстоятельств), на чем многие проекты начинаются, заканчиваются и терпят крах.

В языках программирования, которые не поддерживают в программах принцип ППК, на этом можно было бы и остановиться – и это неплохо. В конце концов, принцип ППК относится к методикам проектирования. Даже без автоматической проверки вы можете помещать контракт в текст программы (как комментарий) и все равно получать от этого реальную выгоду. По меньшей мере, закомментированные контракты дают вам отправную точку для поиска в случае возникновения неприятностей.

Утверждения

Документирование этих предположений уже само по себе неплохо, но вы можете извлечь из этого еще большую пользу, если заставите компилятор проверять имеющийся контракт. Отчасти вы можете эмулировать эту проверку на некоторых языках программирования, применяя так называемые утверждения (см. Программирование утверждений). Но почему лишь отчасти? Разве вы не можете использовать утверждения для всего того, на что способен принцип ППК?

К сожалению, ответ на этот вопрос отрицательный. Для начала, не существует средств, поддерживающих распространение действия утверждений вниз по иерархии наследования. Это означает, что если вы отменяете метод базового класса, у которого имеется свой контракт, то утверждения, реализующие этот контракт, не будут вызываться корректно (если только вы не продублируете их вручную во вновь написанной программе). Не забывайте, что прежде чем выйти из любого метода необходимо вручную вызвать инвариант класса (и все инварианты базового класса). Основная проблема состоит в том, что контракт не соблюдается автоматически.

Кроме того, отсутствует встроенный механизм «старых» значений; т. е. значений, которые существовали на момент входа в метод. При использовании утверждений, обеспечивающих соблюдение условий контрактов, к предусловию необходимо добавить программу, позволяющую сохранить любую информацию, которую вы намерены использовать в постусловии. Сравним это с iContract, где постусловие может просто ссылаться на variabie@pre, или с языком Eiffel, который поддерживает принцип old expression.

И наконец, исполняющая система и библиотеки не предназначены для поддержки контрактов, так что эти вызовы не проверяются. Это является серьезным недостатком, поскольку большинство проблем обнаруживается именно на стыке между вашей программой и библиотеками, которые она использует (более детально этот вопрос обсуждается в разделе Мертвые программы не лгут).

Курс: Создаем бизнес-процессы в Битрикс24. Переменные, константы, циклы, … (3/8)


Похожие статьи.

Понравилась статья? Поделиться с друзьями: