Проектирование по
    контракту
      Сергей Тепляков, Visual C# MVP
              .NET Architect at Luxoft
        SergeyTeplyakov.blogspot.com
Agenda
• Базовые понятия контрактного
  программирования
• Практические аспекты
• Библиотека Code Contracts
• Ограничения контрактов
Определение
Проектирование по контракту
(Design by Contract, DbC) – это
формализация отношений между
программным компонентом и его
клиентами
А нужно ли это?
Что скажете?
public interface IRepository
{
    Customer GetCustomer(string id);
    void SaveCustomer(Customer c);
}
Стандартное решение
• «Комментарии не лгут»
• На крайний случай «Use the Source
  Luke!»
Давайте добавим немного
     формальности!
О корректности ПО
• Код сам по себе, не является
  корректным или некорректным!
• Важна спецификация
• «Задокументированный баг – это
  фича!»
Утверждения в контрактах
• Предусловия
• Постусловия
• Инвариант класса
• Утверждения
• Инвариант цикла (Eiffel specific)
Внедрение спецификации в
код
class Repository : IRepository
{
    public Customer GetCustomer(string id)
    {
        Contract.Requires(id != null);
        Contract.Ensures(
             Contract.Result<Customer>() != null);
    }
}
Нарушения утверждений
• Нарушение предусловия – «баг» в
  клиенте коде
• Нарушение постусловия, инварианта
  или утверждения – «баг» в сервисе
Инструменты DbC
• Утверждения
• Статический анализатор
• Документация (DRY)
Контракты – это общее
понятие, которое покрывает
   многие известные ОО
         принципы
Принцип замещения Лисков
...если для каждого объекта o1 типа S
существует объект o2 типа T такой, что для
всех программ P, определенных в терминах T,
поведение P не изменяется при замене o2 на o1,
то S является подтипом (subtype) для T.
Принцип замещения Лисков
Метод Add
• ICollection.Add
• IList.Add
• Может ли метод добавлять 2
  элемента?
• Или не добавлять ни одного?
Правила наследования
• Наследник может
 • ослабевать предусловие
 • усиливать постусловие
• Инварианты суммируются
Сильные и слабые условия
• X > 0 (слабее)
• X > 5 (сильнее, строже)
Предусловие и наследование
Отношение между типами
Ковариантность по типу
возвращаемого значения
Контравариантность
Action<in T>
static void Foo(object obj) { }

//   Контравариантность аргументов:
//   предусловие делегата Action<object> слабее
//   предусловия Action<string>,
//   поскольку typeof(object) > typeof(string)

Action<string> action1 = Foo;
// Или
Action<string> action2 = new Action<object>(Foo);

action1("Hello Foo!");
Ковариантность Func<out T>
static string Boo() { return "Boo"; }

// Ковариантность возвращаемого значения:
// постусловие делегата Func<string> сильнее
// постусловия Func<object>,
// поскольку typeof(string) < typeof(object)

Func<object> func1 = Boo;
// Или
Func<object> func2 = new Func<string>(Boo);
Ковариантность
исключений
class Base {
public:
virtual void Foo() throw(std::exception) {}
};

class Derived : public Base {
public:
/*override*/ void Foo() throw(std::logic_error) {}
};
Принцип самурая
Мониторинг утверждений
во время выполнения
• Уровни утверждений
  • None
  • Requires
  • Ensures
  • Full
• Assert on Contract Failures
Утверждения и внешние
данные
Контракты vs Защитное
программирование

      Проверка    Проверка при
  предусловий     каждом
   (2 открытых    обращении к
        метода)   полю (27 мест!)
Code Contracts
• Частичная поддержка в .NET 4.0
• Устанавливается отдельно:
 • Статический анализатор
 • Rewriter
 • Генератор документации
Вернемся к методу Add
// From mscorlib.Contracts.dll
[ContractClassFor(typeof (ICollection<>))]
internal abstract class ICollectionContract<T> : ICollection<T>
{
    public void Add(T item)
    {
        // Исходный контракт
        Contract.Ensures(Count >= Contract.OldValue<int>(Count),
                "this.Count >= Contract.OldValue(this.Count)");

        // Мы могли бы добавить!
        Contract.Ensures(Contains(item),
                 "Contains(item) should be true!");
    }
}
Адаптация существующего
кода
 • Уже есть класс Guard?
 • Добавляем атрибут и получаем
   предусловие!
Code Contracts
  • Contract.Requires
  • Contract.Ensures
  • Contract.Invariant
  • Contract.Assert/Assume
Альтернативная проверка
предусловий
 public class SimpleClass
 {
     public int Foo(string s)
     {
         if (s == null)
             throw new ArgumentNullException("s");
         Contract.EndContractBlock();

         return s.Length;
     }
 }
Альтернативная проверка
предусловий
 public static class Guard
 {
     [ContractArgumentValidatorAttribute]
     public static void IsNotNull<T>(T t) where T : class
     {
         if (t == null)
             throw new ArgumentNullException("t");
         Contract.EndContractBlock();
     }
 }
Ограничения DbC
• Аккуратнее со статическим
  анализатором!
• Не переусердствуйте в
  формализации
• Частичная поддержка Code
  Contracts в .NET Framework
Важнейшие принципы DbC
• Разделение ответственности
• Упрощение обязанностей
• Обобщение существующих понятий
• Формализация отношений
Дополнительные материалы
• Бертран Мейер, “Объектно-ориентированное конструирование
  программных систем”
• С. Тепляков, “Проектирование по контракту”, RSDN Magazine #1-
  2010
• Programming Stuff. “Альтернативная проверка предусловий в Code
  Contracts”
• Programming Stuff. “Принцип замещения Лисков и контракты”
• Programming Stuff. “Принцип самурая”
• Programming Stuff. “Как не надо писать код”
• Александр Бындю. “Дополнение к LSP”
Спасибо за внимание

   • Сергей Тепляков, Visual C# MVP
        • .NET Architect at Luxoft
     • Sergey.Teplyakov@gmail.com
• http://sergeyteplyakov.blogspot.com/

Design by Contract

Editor's Notes

  • #5 Такие слова, как «формализация» или «спецификация» чужды и страшны большинству разработчиков. Сразу же возникает вопрос: а стоит ли затевать весь этот сыр-бор ради этого? Чем не подходят старые, проверенные временем практики, без всяких там контрактов?
  • #8 А что если описать отношения между классом (или интерфейсом) и его клиентами более формальным образом.Например, можно сказать, что я сделаю свою работу, но только в том случае, если свою работу сделает вызывающий код.Таким образом, мы четко разделим отношения класса и его клиенты; каждый из них будет точно знать, на что рассчитывать от другой стороны и не будет перепроверять это по десять раз.Кстати, обратите внимание, что я не говорю о тотальной формализации отношений, зачастую достаточно разделить эти обязанности в вашей голове, чтобы такое разделение нашло отражение в вашем коде и дизайне.Но прежде чем переходить к способам разделения ответственностей, давайте рассмотрим еще одну важную характеристику.Я думаю, что многие разработчики спорят друг с другом во время код или дизайн ревью (или с представителями отдела качества) о том, содержит ли баг некоторый фрагмент кода или нет.
  • #14 Сейчас существует огромное множество различных паттернов, принципов, идиом, которые должны помочь в создании хорошего дизайна. Я ничего не имею, например, против принципов SOLID, но у меня есть совет, как их лучше запомнить и, главное, как ими лучше пользоваться на практике.На самом деле, контрактное программирование (прошу, не путайте с конкретными библиотеками, типа Code Contracts) – это отличный инструмент в борьбе за хороший дизайн.Для начала, давайте рассмотрим один из SOLID принципов: принцип замещения Лисков. Именно на нем строится очень важный аспект любой ОО модели, поскольку он лежит в основе полиморфного поведения.Может стоит сказать о том, что в самой навороченной книге по ООП – в книге Бертрана Мейера описано очень мало разных принципов. Из известных есть только Open Closed.