На самом деле пытается использовать 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, редактирование и продолжение не могут быть использованы
- Контракты очень сильно привязаны к принципу подписи Лискова: подтипы никогда не могут смягчить предварительные условия. Я имею в виду, в принципе, хорошо, но я думаю, что это было бы болью при работе со сверхзатратным сторонним кодом.
Ответ 4
Я создал плагин для Visual Studio 2010, который может сэкономить вам некоторое время на создание кодовых контрактов для интерфейсов и абстрактных классов: http://visualstudiogallery.msdn.microsoft.com/en-us/d491911d-97f3-4cf6-87b0-6a2882120acf
Ответ 5
На самом деле, я нахожу реализацию интерфейса великолепной. Это не загромождает ваш интерфейс или ваш обычный код, и вы можете аккуратно хранить его в какой-либо папке в своем решении или в том же файле класса, что и ваш интерфейс, в зависимости от того, что вам больше нравится.
Зачем вам требуется добавить инвариант объекта? Вам нужно только добавить столько, сколько вам нужно.
Я понимаю вашу мысль о раздувании кода, но я думаю, что компромисс с кодовыми контрактами. Дополнительные проверки/средства защиты означают дополнительные коды, по крайней мере, как это реализовано в настоящее время. Я бы постарался сохранить как можно больше контрактов на интерфейсах, что, по крайней мере, поможет вам облегчить боль набухания кода.
Ответ 6
Если вы беспокоитесь о раздувании кода, я бы рекомендовал вам в качестве альтернативы рассмотреть Aspect Oriented Programming.
Это позволит вам указать контракт в аспекте, а затем обернуть любые классы или методы классов в контракте. Если ваш контракт будет использоваться более чем в одном классе, это будет более чистым способом реализации этого.
К сожалению, поддержка AOP в С# невелика, но есть несколько библиотек, которые добавляют эту функциональность.
И поскольку у меня также есть то же чувство, что и в других реализациях С#, как вы нашли с Контрактами, все сначала кажется прекрасным, пока вы не поднимете капот и не начнете его использовать серьезно.