(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Альтернативная проверка предусловий в Code Contracts

При попытке использования библиотеки Code Contracts в реальном проекте может возникнуть небольшая сложность: хотя сам класс Contract с методами проверки предусловий и постусловий, располагается в mscorlib начиная с 4-й версии .NET Framework, но без установки самой библиотеки Code Contracts, они не попадают в результирующую сборку.

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

 Однако Code Contracts поддерживает дополнительный "режим совместимости", который позволяет "жестко зашить" проверки предусловий в результирующий код, так что они будут видны всем, не зависимо от того, установлены контракты на машине разработчика или нет.

Постановка проблемы

 Давайте вначале рассмотрим пример, который более четко покажет, в чем проблема.

class SimpleClass
{
    public int Foo(string s)
    {
        Contract.Requires(s != null);
        return s.Length;
    }
}

 С этим кодом совершенно все в порядке и при попытке вызова метода Foo с null, мы получим нарушение контракта, что при установленной библиотеке Code Contracts и включенной проверке предусловий приведет к генерации исключения 'System.Diagnostics.Contracts.__ContractsRuntime.ContractException'.

 Да, именно этого мы и ждем, но особенность заключается в том, что код генерации исключения генерируется не компилятором, а отдельным процессом, который запускается сразу после компиляции. А это значит, что без библиотеки Code Contracts, выполнение этого кода приведет к генерации NullReferenceExcpetion, поскольку никакой дополнительной валидации аргументов не останется и в помине. Я неоднократно сталкивался с тем, что такое поведение вызывало примерно такую реакцию: "WTF? Куда делась моя проверка!"

 Поскольку мы не хотим слышать подобные "WTF?!?" от наших коллег, у которых контракты не установлены, то хотелось бы иметь способ зашить проверку предусловий более основательным образом.

Ручная проверка предусловий

 Библиотека Code Contracts позволяет использовать предусловия в старом формате. Это значит, что если существующий метод уже содержит проверку входных параметров (т.е. проверку предусловий), то для преобразования их в полноценные предусловия после них достаточно добавить вызов Contract.EndContractBlock:

public class SimpleClass
{
    public int Foo(string s)
    {
        if (s == null)
            throw new ArgumentNullException("s");
        Contract.EndContractBlock();
 
        return s.Length;
    }
}

 Добавление вызова Contract.EndContractBlock превращает одну (или несколько) проверок входных параметров в полноценные предусловия. Теперь, для любого разработчика, у которого контракты не установлены, этот код будет выглядеть, как и раньше. В то время, как обладатели контрактов, смогут пользоваться всеми их преимуществами, такими как проверка валидности программы с помощью Static Checker-а, автоматическая генерация документации, возможность отлова всех нарушений контрактов (подробнее об этом будет ниже). Отличие этого способа проверки лишь в том, что их нельзя отключить и выпилить из кода полностью.

 Данный подход можно совмещать с более продвинутыми техниками использования контрактов. Так, например, можно совмещать old-style проверку предусловий совместно с проверкой постусловий и инвариантов. Но поскольку постусловия и инварианты в большей мере касаются самого класса, а не его клиентов, то это никак не затронет всех тех разработчиков, у которых контракты не установлены.

ПРИМЕЧАНИЕ
 Библиотека Code Contracts позволяет настраивать, какие проверки должны оставаться в результирующем коде. Если разработчики достаточно уверены в своем коде, то они могут убрать все проверки из кода и сэкономить несколько тактов процессора на каждой из них. Более подробно об уровнях мониторинга и возможностях по их управлению можно почитать в статье: "Мониторинг утверждений в период выполнения".

Использование существующих методов проверки

 Еще одним стандартным способом валидации аргументов является использование специальных классов (guard-ов) с набором разных методов, типа NotNull, NotNullOrEmpty и т.п. Библиотека Code Contracts поддерживает возможность превращения подобных методов в полноценные контракты: для этого методы класса валидатора нужно пометить атрибутом ContractArgumentValidatorAttribute.

ПРИМЕЧАНИЕ
 К сожалению атрибут ContractArgumentValidatorAttribute не входит в состав .NET Framework версии 4.0, он появится только в версии 4.5. Разруливается эта ситуация путем добавления в ваш проект файла ContractExtensions.cs, который появится в %ProgramFiles%\Microsoft\Contracts\Language\CSharp после установки библиотеки Code Contracts.

public static class Guard
{
    [ContractArgumentValidatorAttribute]
    public static void IsNotNull<T>(T t) where T : class
    {
        if (t == null)
            throw new ArgumentNullException("t");
        Contract.EndContractBlock();
    }
}

 Теперь мы можем использовать старый добрый метод IsNotNull для проверки предусловий:

public int Foo(string s)
{
    Guard.IsNotNull(s);
 
    return s.Length;
}

Отступление от темы. Contract.ContractFailed

 Возможно, вы обращали внимание на существование двух версии метода Contract.Requires, одна из которых является обобщенной (generic) и может использоваться для генерации нужного типа исключения, нарушение же необобщенной версии приводит к генерации внутреннего (internal) исключение типа ContractException.

 Причина, по которой по умолчанию генерируется внутреннее исключение, заключается в том, что нарушение контракта не может быть восстановлено программным путем. Это баг в коде и для его устранения необходимо изменение этого кода. Однако при использовании любого подхода к проверке предусловий (Contract.Requires + 2 рассмотренных сегодня подхода), пользователь может "захавать" исключение, перехватив базовый тип исключения.

 В классе Contract есть событие ContractFailed, которое будет вызываться при нарушении предусловий/постусловий/инвариантов. Например, перед запуском интеграционного или юнит-теста можно подписаться на это событие, и если падает предусловие, но тест остается зеленым, то можно закатывать рукава и идти искать того нерадивого программиста, который ловит исключения, не предназначенные для обработки.

Заключение

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

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 16.03.2012 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Microsoft Office 365 Бизнес. Подписка на 1 рабочее место на 1 год
Microsoft 365 Business Standard (corporate)
Microsoft 365 Apps for business (corporate)
Microsoft Windows Professional 10, Электронный ключ
Microsoft Office 365 Профессиональный Плюс. Подписка на 1 рабочее место на 1 год
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
Все о PHP и даже больше
Мастерская программиста
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100