Проектирование по контрактам и конструкторам

Я использую свой собственный ArrayList для школьных целей, но для того, чтобы немного оживить вещи, я пытаюсь использовать С# 4.0 Code Contracts. Все было в порядке, пока мне не пришлось добавлять Контракты к конструкторам. Должен ли я добавлять Contract.Ensures() в пустой конструктор параметров?

    public ArrayList(int capacity) {
        Contract.Requires(capacity > 0);
        Contract.Ensures(Size == capacity);

        _array = new T[capacity];
    }

    public ArrayList() : this(32) {
        Contract.Ensures(Size == 32);
    }

Я бы сказал, да, у каждого метода должен быть четко определенный контракт. С другой стороны, зачем ставить его, если он просто делегирует работу "главному" конструктору? По логике, мне не нужно будет.

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

Кроме того, есть ли какие-нибудь книги вокруг, которые немного глубже относятся к принципам и использованию Design by Contracts? Одна вещь - знание синтаксиса использования Контрактов на языке (С# в данном случае), другое - знание того, как и когда его использовать. Я прочитал несколько уроков и статью о нем в статье "Глубина", но я хотел бы пойти немного глубже, если это возможно.

Спасибо

Ответ 1

Я полностью не согласен с Томасом. Пока вы делаете выбор в реализации ArrayList(), у вас должен быть контракт на него, который документирует эти варианты.

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

Ответ "он называет главный конструктор, поэтому пусть контракт основного конструктора выполняет эту работу" полностью игнорирует тот факт, что контракт заключается в том, чтобы избавить вас от необходимости смотреть на реализацию. Для стратегии проверки, основанной на проверке проверки выполнения во время выполнения, недостатком написания контрактов даже для таких коротких конструкторов/методов, которые почти непосредственно называют другой конструктор/метод, является то, что вы в конечном итоге проверяете вещи дважды. Да, это кажется излишним, но проверка выполнения во время выполнения является только одной стратегией проверки, а принципы DbC независимы от нее. Принцип: если его можно назвать, ему нужен контракт, чтобы документировать, что он делает.

Ответ 2

Клиентский код (с использованием Code Contracts), который использует ArrayList, не будет знать, что пустой конструктор Ensure, который Size == 32, если вы явно не указали это с помощью Ensure.

Итак (например):

var x = new ArrayList();
Contract.Assert(x.Size == 32)

предоставит вам предупреждение "assert not tested".

Вам нужно явно указать все контракты; код-сценарист/статический контролер не будет "просматривать" метод, чтобы увидеть какие-либо последствия — см. мой ответ на соответствующий вопрос: "Нужно ли указывать Contract.Requires(...) избыточность в делегировании?"

Ответ 3

Я рекомендую читать Object Oriented Software Construction, 2nd Edition или, возможно, Touch of Class, как от Bertrand Meyer. В качестве альтернативы вы можете прочитать статью 1992 года Применение "Дизайн по контракту" от того же автора.

Подводя итог:

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

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

В этом ответном ответе я обсуждал похожие темы, включая пример.

Ответ 4

Umh, я не совсем понимаю, почему вы поставили 'Ensures' также в C'tor по умолчанию. Поскольку он вызывает основной c'tor, который уже выполняет полный контракт, по умолчанию c'tor делает это также - по определению. Так что это логическая избыточность и, следовательно, большой "Do not". Может быть, это может иметь прагматические последствия, как вы говорите, - не знаю, какие кодовые контракты хороши...

В отношении литературы - лучшие источники:

НТН! Томас

Ответ 5

Дизайн по контракту исходит из математических корней функционального программирования: Предпосылки и Постусловия.

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

Так как меня научили этому в той степени, в которой я сейчас участвую, могут быть лучшие определения. Статья Википедии о разработке по контракту написана с наклонением OO, но pre/postconditions не зависят от языка.