Понятие контрактное программирование появилось

Programming stuff

Тут народ на rsdn-е интересуется, а стоит ли пользоваться Code Contracts, вопрос интересный. Ответ такой вопрос звучит обычно так:“it depends”, но я могу помочь понять, от чего и что depends.

Краткий экскурс в историю контрактов

Сама идея контрактного программирования появилась в 80-х годах в голове Бертрана Мейера и основывалась на матанских выкладках Хоара и других умных дядек.

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

Для выражения ожидаемого поведения (a.k.a. спецификации) использовались утверждения, а чтобы понять, кто в программе не прав, вызывающий или вызываемый код, то добавлено несколько видов утверждений:

Инварианты класса – нарушения которых также говорят о баге в вызываемом коде. Это ОО-специфическая штука, которая позволяет четко сказать, чем же является валидное состояние объекта, чтобы не множить предусловия и постусловия. Тут нужно помнить, что инвариант валиден от момента создания объекта до момента его уничтожения – вызова деструктора/метода Dispose, но может быть не валидным *внутри* вызова метода. Инвариант толжен быть валидным в *видимых точках*.

Утверждения о корректности реализации – нарушения которых также говорят о баге в вызываемом коде. Это старые добрые ассерты, растыканые в дебрях реализации, и упрощающие отлов багов.

Читайте также:  Программирование ардуино через ардуино уно

Инварианты цикла – экзотическая штука, которая «доказывает» о сходимости цикла. Есть только в Eiffel и их можно игнорировать.

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

Поскольку утверждения (assertions) – вещь не новая, то в чем же разница между контрактами и старыми добрыми assert-ами? А разница в том, что контрактное программирование подразумевает «интеграцию» утверждений в инструменты, код и мозг программиста.

Возможность задавать уровень утверждений: убрать всё; оставить только предусловия; оставить предусловия и постусловия; оставить все утверждения.

Возможность генерации документации и удобный способ «обнаружения» контрактов в среде исполнения. Тот самый тулинг!

Любая полноценная реализация «контрактов» должна обладать всеми этими характеристиками, ибо, как пишет пророк-контрактов-Мейер, именно в объединении всех этих идей и заключается сила (хотя бытует мнение, что сила в правде!).

С момента публикации разных букварей о контрактном программированя и появлении подобных тулов прошло много времени, но идея нашла лишь частичное примерение. Почему – ниже. Сейчас же можно на пальцах руки пересчитать «полноценные реализации», толковых из которых чуть менее, чем ноль. Есть Eiffel с полной поддержкой контрактов, есть языки с предусловиями/постусловиями и без возможности задавать поведение нарушений во время исполнения и без статической верификации, и есть ряд тулов для платформ .NET и Java.

Краткий экскурс в историю Code Contracts

Поскольку идея контрактного программирования звучит здраво, не удивительно, что умные мужи во всяких крупных компания решили взять молоток и сделать свою реализацию. В Microsoft этим занимались ребята из исследовательского подразделения (a.k.a. Microsoft Research) в размере, человек, кажется, трех.

К моменту начала работы над библиотекой, уже была запилена поддержка контрактов в языке Spec#, что дало понять, что идея работает. Но здравости идей не достаточно, чтобы можно было убедить Хейлсебрга и компанию в добавлении контрактов в мейнстрим языки, типа C#/VB. Что делать? Тогда было принято осознанное решение сделать контракты language agnostic – привязать их к платформе и сделать доступными для любого языка платформы .NET (ну, как мы потом узнаем, это работает скорее в теории).

И что это означает с точки зрения реализации? Это значит, что «контракты» должны работать на IL-уровне: декомпилировать существующий код, находить вызовы нужных методов (методы класса System.Diagnostics.Contracts.Contract) и переписывать их в зависимости от настроек пользователя – в классические утверждения, в генерацию исключения или просто выпиливать эти вызовы к чертям. Или же находить контракты и пытаться доказать, что программа корректна или нет (статик чекер).

Звучит довольно просто, и процесс чтения и переписывания IL-а и был довольно простым, в году эдак 2003-м, до появления анонимных методов с замыканиями, до появления блоков итераторов и асинк методов. Теперь представьте себе, что три ресечера занимаются тулом десять лет, когда кишки платформы претерпевают существенные изменения, выходят новые тулы для чтения IL-а, а сам генерируемый IL периодически меняется при добавлении новых возможностей. Тогда станет понятным, что метод типа MikesArchitecture – это вполне нормально, что форматирование – это для трусов, и что разобраться в логике переписывателя без слабых наркотиков не получиться. Не говоря уже о статическом анализаторе, понять (и простить?) который сможет лишь PhD под чем-то очень ядреным.

Адаптация внутри Microsoft-а

Если посмотреть на http://referencesource.microsoft.com, то можно найти с тысячу ссылок на Contract.Requires и пару тысяч вызовов Contract.EndContractBlock внутри .NET Framework . Это все равно очень мало, поскольку валидация аргументов – это наше все, а контракты – это наше все для валидации аргументов.

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

Культура в Microsoft-е такова, что там нет возможности навязать команде какие-то инструменты. Если инструмент команде нравится, то она им будет пользоваться. Если нет (и это не противоречит некоторым глобальным стандартам), то инструмент использоваться не будет.

К сожалению, с контрактами, особенности реализации и «исследовательский подход», сделали свое дело. Code Contracts даже сейчас обладают посредственной производительностью, влияют на ран-тайм поведение «переписанного» кода, не обладают нормальным тулингом и не позволяют использовать статический анализатор в промышленных масштабах. Идея-то прекрасная, но реализация четко соответствует вложенным в него средствам (хотя я бы сказал, что для инструмента, который вышел из под пера трех человек – результат превышает вложенные финансовые средства!).

А раз не было адаптации крупными подразделениями, не последовали дополнительные инвестиции, которые бы позволили довести тул до ума. Проблема курицы и яйца в чистом виде.

Facts and Fallacies (факты и заблуждения, кажется)

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

Статический анализатор не работает.
Это тоже верно. Cccheck выдает много ложных предупреждений и может потребоваться серьезный тюнинг кода, чтобы рассказать анализатору, что все нормально, и это он дурак. В результате cccheck на реальных крупных проектах используется редко или запускается избирательно для анализа кода раз в пару месяцев.

Тулинг желает лучшего.
И снова верно. Есть Code Contracts Editor Extensions, но эта штука у меня никогда толком не работала. Поэтому я взялся за плагин к Решарперу, но эта вещь весьма ограниченная.

Результирующий код работает медленнее.
Это не совсем верно. В моем текущем проекте народ просто повернут на эффективности – кастомные сериализаторы, сотни структур и человеко-год оптимизаций. Но контракты все еще есть. Главная проблема в контрактах была связана с recursion check-ом, который вставляется rewriter-ом, когда в предусловии используется член текущего класса. Выключение этой проверки дало 15% прироста производительности в тяжеловесном end-to-end сценарии, а полное отключение контрактов после этого дало еще 5-6%.

Генерация внутреннего ContractException – это страшный баг реализации.
Ну, это by design. Если хочется обрабатывать исключение, то можно использовать Contract.Requires>(predicate), но в большинстве случаев нарушение предусловия или постусловия должно лететь по стеку на самый верх и там уже либо крэшить приложение или выдавать сообщение пользователю о внутренней ошибке.

Зачем контракты, можно и if-throw-ми обойтись.
Ну, следуя такой логике, можно и на ассемблере писать. Я с этим утверждением не согласен. Думать в терминах контрактов – очень полезно, и меня лично использование инструмента стимулирует. Плюс возможность вырубить контракты целиком или подписаться на нарушение контракта – бывает очень полезным.

Переходить на контракты сложно. Это же все менять нужно!
Это тоже не совсем верно, точнее совсем не верно. Есть несколько простых шагов по безболезненной адаптации этой тулы. Начать проще всего с декорации существующих классов, типа Guard.NotNull атрибутом ContractArgumentValidatorAttribute (подробнее – тут), или путем добавления Contract.EndContractBlock после обычного if-throw. В этом случае, даже если библиотека Code Contracts не будет установлена на машине разработчика, поведение во время исполнения останется старым, а вы сможете получить новые плюшки (типа постусловий, инвариантов, генерации документации или даже статического верификатора).

Будущее

С будущим все довольно сложно.

Библиотека Code Contracts перешла в открытый доступ и ее развитием занимается комьюнити. После выпуска поддержки VS2015 это самое комьюнити достаточно неплохо активизировалось – появляются баги и, что не менее важно, появляются фиксы. Я планирую в ближайшее время допилить нормальные постусловия для асинхронных методов. Есть еще планы по доработке тулинга.

В ближайшее время репозиторий переедет в dotnet организацию на github-е, что упростит некоторые организационные вопросы. С точки же зрения официального развития со стороны Microsoft, то оно, скорее всего, закончено.

Но помимо Code Contracts, есть теоретические планы по добавлению контрактов в новую редакцию языка C# (вот обсуждение на гитхабе). Но пока что совсем не ясно, что из этого выйдет, каким функционалом будут обладать новые контракты, когда они появятся, и появятся ли они вообще.

Так использовать или нет?

Я всегда стараюсь разделять инструмент от практик разработки. Понятно, что в некоторых случаях отсутствие инструмента делает практику бесполезной, но это не тот случай.

Смысл-то не столько в Code Contracts, как в библиотеке, сколько в Design by Contracts, как способе разделения ответственности между программными компонентами. Берем новый класс. Задаем себе вопрос: а какой у него контракт? Что за предусловия, какие постусловия, насколько инварианты его простые и очевидные? Если ответить на эти вопросы нельзя, то инструмент не очень-то поможет. Нужно дизайн править!

Ну а если таки хочется попробовать инструмент (что очень даже хорошо!), то можно начать им пользоваться по полной в своих домашних проектах (как это их у вас нет. ) или использовать плавный путь, описанный ранее, с помощью аннотации Guard.NotNull или путем использоваться Contract.EndContractBlock. Тогда, если со временем польза станет очевидной, то можно будет переключиться на полноценное использование этой тулы, а если не взлетит, то и удалять ничего не придется, поскольку Contract.EndContractBlock есть не просит.

Да, если что, это все мое личное мнение, официальное мнение Microsoft может быть похожим, не похожим, или вообще не знаю каким.

Источник

Оцените статью