На самом деле пытается использовать CodeContracts в С#

Я, наконец, играю в catchup со всем новым, добавленным в .NET 3.5/4.0 Framework. Последние несколько дней я работал с CodeContracts, и я действительно очень стараюсь их любить. Мне любопытно, что другие люди думают о реализации CodeContracts в С#? В частности, как люди организуют такие вещи, как классы контрактов для интерфейсов, методы контрактов для контрактных инвариантов и т.д.?

Мне нравится подтверждение, которое предоставляют контракты, на первый взгляд они выглядят великолепно. С помощью нескольких простых строк я могу получить хорошую проверку сборки, прежде чем я даже запустил свой код. К сожалению, мне сложно преодолевать чувство, что способ, которым заключаются контракты на коде, реализуется на С#, они забивают мой код больше, чем документируют контракты. И чтобы в полной мере воспользоваться контрактами, я замалчиваю свой код предположениями и утверждениями и т.д. (Что я знаю, что некоторые скажут, что это хорошо); но, как показывают некоторые из моих примеров ниже, он превращает одну простую строку в 4 или 5 строк и на самом деле не добавляет достаточного значения по сравнению с альтернативными подходами (т.е. утверждает, исключения и т.д.).

В настоящее время мои самые большие разочарования:

Контракт интерфейса:

[ContractClass(typeof(IInterfaceContract))]
  public interface IInterface
  {
    Object Method(Object arg);
  }

  [ContractClassFor(typeof(IInterface))]
  internal abstract class IInterfaceContract
  {
    private IInterfaceContract() { }

    Object IInterface.Method(Object arg)
    {
      Contract.Requires(arg != null);
      Contract.Ensures(Contract.Result<Object>() != null);
      return default(Object);
    }
  }

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

Код Bloat:

typeof(Action<>).MakeGenericType(typeof(Object);

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

var genericAction = typeof(Action<>);

Contract.Assume(genericAction.IsGenericType);
Contract.Assume(genericAction.GetGenericArguments().Length == 1);

genericAction.MakeGenericType(typeof(Object));

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

Кроме того, взяв такой случай, как

  public class MyClass
    : IInterface
  {
    private readonly Object _obj;

    public Object Property
    {
      get
      {
        Contract.Ensures(Contract.Result<Object>() != null);
        return _obj;
      }
    }

    public MyClass(Object obj)
    {
      Contract.Requires(obj != null);

      _obj = obj;
    }
  }

obj требуется, чтобы он был не равным null и был установлен в поле только для чтения, которое не может быть изменено, но мне все еще требуется добавить метод "разметки" в мой класс, чтобы мое требование к свойствам не возвращать значение null могло быть доказано

[ContractInvariantMethod]
private void ObjectInvariant()
{
  Contract.Invariant(_obj != null);
}

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

Спасибо!

Ответ 1

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

Команда CC заявила, что использование атрибутов просто недостаточно мощно, потому что вы не можете включать в них такие вещи, как лямбды. Они могут включать такие вещи, как [NotNull], но выбрали не делать этого, потому что они пытаются сохранить CC как можно более общим.

Одна из причин, по которой CC является библиотекой (а не частью расширенного С#), заключается в том, что она поддерживается на всех языках .NET.

Вы можете узнать больше о рассуждениях команды здесь.

С точки зрения фактического использования этого, я до сих пор поддерживал свои контракты интерфейса в том же файле, что и интерфейс, что означает, что все это документировано в одном месте. Это то, что нужно улучшить:)

Код Bloat [...]

Ваша вторая жалоба, вероятно, может быть реализована - я предлагаю опубликовать ее на форуме контрактов с кодами. (EDIT: Кажется, у кого-то уже есть, но ответов пока нет.)

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

Кроме того, принимая случай типа [...]

Этот был рассмотрен. Если у вас есть свойство auto-property, вам просто нужно добавить ненулевой инвариант, и будут созданы предварительные/пост-условия:

public class MyClass : IInterface
{
    private Object Property { get; set; }

    [ContractInvariantMethod]
    private void Invariants()
    {
        Contract.Invariant(Property != null);
    }
}

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

Ответ 2

Да, контракты обрушиваются на вещи, но я чувствую, что это стоит того, когда PEX читает кодовые контракты и генерирует 25 модульных тестов и 100% покрытие кода для меня. Если вы еще не использовали PEX, то вам не хватает самого большого преимущества кодовых контрактов. Здесь руководство для начинающих для использования

Ответ 3

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

Мне бы очень хотелось, чтобы Microsoft воскресила SpeС#. Я знаю, что кодовые контракты были написаны как библиотека, доступная на разных языках, но даже скорость статического анализа кричит для поддержки компилятора.

Этот парень поднимает одни и те же точки: http://earthli.com/news/view_article.php?id=2183

Во всяком случае, не было ни одной проблемы, которая была нарушителем сделки, но было бы накопление раздражений:

  • Статический анализ очень медленный. Вы можете установить его для запуска в фоновом режиме, но тогда его очень легко игнорировать.
  • Статический анализ невелик, например:
    • Он обрабатывает только простой поток управления
    • Не предполагается, что поля readonly являются инвариантными
    • Коллекции не обрабатываются статической проверкой (только время выполнения)
  • Нет делегированных контрактов
  • Не работает с Resharper, если вы активно запрещаете предупреждения переадресации так или иначе
    • Resharper оптимистичен. Он предположит, что параметр метода не равен null, пока вы не дадите ему намек на то, что он может быть - как Contract.Assume(thing != null)
  • Может генерировать множество предупреждений и предложений, иногда возникающих из неоднозначного источника.
    • В тот момент, когда какой-либо член команды позволяет их обратить внимание, количество предупреждений станет для всех подавляющим.
  • Необходимость указывать абстрактные классы контрактов для интерфейсов - боль.
  • Контракты времени выполнения используют переписывание ИИ, что, по-видимому, не очень хорошо работает с другими переписывающими решениями, такими как PostSharp (я думаю.
    • В результате перезаписи IL, редактирование и продолжение не могут быть использованы
  • Контракты очень сильно привязаны к принципу подписи Лискова: подтипы никогда не могут смягчить предварительные условия. Я имею в виду, в принципе, хорошо, но я думаю, что это было бы болью при работе со сверхзатратным сторонним кодом.

Ответ 5

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

Зачем вам требуется добавить инвариант объекта? Вам нужно только добавить столько, сколько вам нужно.

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

Ответ 6

Если вы беспокоитесь о раздувании кода, я бы рекомендовал вам в качестве альтернативы рассмотреть Aspect Oriented Programming.

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

К сожалению, поддержка AOP в С# невелика, но есть несколько библиотек, которые добавляют эту функциональность.

И поскольку у меня также есть то же чувство, что и в других реализациях С#, как вы нашли с Контрактами, все сначала кажется прекрасным, пока вы не поднимете капот и не начнете его использовать серьезно.