Code Contracts в .NET 4.0
.NET 4.0 в рамках CLR появилась такая новинка как Code Contracts. Что оно такое? Code Contracts это развитие идеи программирования по контракту (Design by Contract), которая была введена Бертраном Мейером, создателем языка Эйфель. Чтобы услышать объяснение того что такое контракты и как они улучшают разработку программного обеспечения можно почитать его интервью.
Контракт – это по сути спецификация компонентов системы. Вот как определили Контрактное программирование в википедии:
Контрактное программирование — это метод проектирования программного обеспечения. Он предполагает, что проектировщик должен определить формальные, точные и верифицируемые спецификации интерфейсов для компонентов системы.
Также как понимает это сам создатель можно прочитать в его интервью.
Итак зачем они нужны? Для того чтобы проверить или верифицировать объекты и компоненты системы, при этом улучшая отладку, тестирование, документирование.
CLR без сode contracts располагает похожим механизмом. Казалось бы, все что привносят сode contracts можно было реализовать до этого с помощью Debug.Assert, Trace.Assert, или throw. Но тем не менее код с сode contracts выглядит намного чище, и прежние ассерты не давали возможности проводить статическую проверку на этапе компиляции, не было возможности создавать автоматически сгенерированы юнит-тесты, не было возможности сгенерировать документацию для объекта с учетом тех спецификаций, которые они накладывают на объект. Теперь же с появлением code contracts все это возможно.
Справедливости ради, следует заметить, что Code contracts достаточно давно существуют как проект в рамках Microsoft Research, но с приходом .NET 4.0 для него наступил звездный час и его внесли в CLR. Хотя мне кажется это вполне логичным развитием как Debug.Assert(Trace.Assert) и if-then-throw контрактов которые появились вместе с самим C#, так и определенная популяризация идея defensive programming.
Давайте посмотрим насколько это возможно.
И хотя сам класс System.Diagnostics.Contracts.Contract включили в состав .NET 4.0, тем не менее, чтобы его использовать необходимо придется либо вручную определить в свойствах проекта символ CONTRACTS_FULL для проверки контрактов в runtime либо загрузить с сайта проекта tools, которые добавят в свойства проекта новую вкладку с названием code contracts. В Visual Studio 2010, релизном варианте, скачанном после запуска, такая вкладка отсутствует.
Давайте попробуем, как ведет себя статическая проверка. Достаточно на этой вкладке отметить чекбокс "Perform static checking" и скомпилировать код с контрактами и/или нарушением этих контрактов. В итоге все проверки появятся, как warnings если было нарушение.
Тестируемость. Например, можно создать автосгенерированные юнит-тесты с помощью такого инструмента как Pex (http://research.microsoft.com/en-us/projects/pex/). На скриншоте pex вывел результат теста, который не прошел проверку по контракту:
Для генерирования документации существует утилита Code Contract Document Generator Tool (CCDocGen.exe). Она добавляет информацию про контракты в уже сгенерированные компилятором файлы XML документации, а чтобы из XML файла получить, например, документацию в стиле MSDN достаточно воспользоваться утилитой SandCastle (http://sandcastle.codeplex.com/). Хотя помимо сгенерированой документации, сам код является в некотором роде самодокументированым. Т.е. достаточно одним глазом взглянуть на контракты чтобы определить каким требованиям должны соотвествовать объекты.
Итак, преимуществ перед старыми средствами достаточно много.
Code contracts есть 3 видов:
- Preconditions – используется для валидации аргументов
- Postconditions – для проверки состояния по завершению метода, независимо от того нормально он завершился или с исключением
- Object invariants – для проверки, что данные объекта находятся в хорошем состоянии на протяжении жизни объекта
Если одно из этих условий нарушается, то при runtime проверке мы получим ContractException. Существует также возможность подписаться на событие Contract.ContractFailed чтобы либо продолжить/прервать выполнение, либо отреагировать на нарушение контракта. Например подписку на ContractFailed можно использовать, например для того чтобы юнит-тесты не остановили процесс сборки тестов на билд машине.
Если контракт будет нарушен в runtime то мы увидим стандартный для ассертов диалог.
Контракты очень похожи на ассерты поэтому для их понимания давайте лучше по смотрим на них в действии. Итак, пусть у нас есть объекты Order и OrderItem.
public static class RuntimeFailureMethods
{
public static void Requires(bool cond, string userMsg, string condText)
{ /*...*/ }
public static void Requires
where E : Exception
{ /*...*/ }
public static void Ensures(bool cond, string userMsg, string condText)
{ /*...*/ }
public static void EnsuresOnThrow(bool cond, string userMsg, string condText, Exception innerException)
{ /*...*/ }
public static void Assert(bool cond, string userMsg, string condText)
{ /*...*/ }
public static void Assume(bool cond, string userMsg, string condText)
{ /*...*/ }
public static void Invariant(bool cond, string userMsg, string condText)
{ /*...*/ }
public static void ReportFailure(ContractFailureKind kind, string userMsg, string condText, Exception inner)
{ /*...*/ }
public static string RaiseContractFailedEvent(ContractFailureKind kind, string userMsg, string condText, Exception inner)
{ /*...*/ }
public static void TriggerFailure(string message, string userMsg, string condText, Exception inner)
{ /*...*/ }
}
Если какие-либо методы пропущены, то они либо будут синтезированы либо исопользованы стандартные методы. Класс с custom rewriters methods может лежать как в отдельной сборку так и в основной, главное чтоб rewriter смог его найти.
Code contracts несомненно полезная штука, которую будут использовать и возможно в будущем она полностью заменит Debug.Assert/Trace.Assert.
Компании из статьи
Microsoft Украина | Украинское подразделение компании Microsoft. |