Я наконец играю в догонялки со всем новым, которое было включено к.NET 3.5/4.0 Платформы. Последние несколько дней я работал с CodeContracts и я действительно очень стараюсь любить их. Мне любопытно, что другие люди думают о реализации CodeContracts в C#? А именно, как люди организуют вещи как классы Контракта для интерфейсов, методы контракта для Инвариантов Контракта и т.д.?
Мне нравится проверка, которую предоставляют контракты, на первый взгляд они выглядят великими. С несколькими простыми строками я могу получить некоторую хорошую сборку, проверяющую, прежде чем я даже выполню свой код. К сожалению, мне нелегко преобладать над чувством, что путем, которые кодируют контракты, является реализация в C#, они загромождают мой код больше, чем они документируют контракты. И в полной мере воспользоваться контрактами, я замусорил свой код предположениями и утверждаю и т.д. (который я знаю, что некоторые скажут, хорошая вещь); но поскольку некоторые мои примеры ниже покажут, это возвращает единственную простую строку к 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);
}
}
Это чувствует себя подобно такому cludge мне, мне жаль, что не было более чистого способа зарегистрировать требования, или через атрибуты или через некоторую форму созданных в поддержке языка. То, что я должен реализовать абстрактный класс, который реализует мой интерфейс, именно так я могу указать, что контракт кажется утомительным в лучшем случае
Чрезмерное увеличение размера кода:
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 требуется, чтобы не быть пустым, и установлен на поле только для чтения, которое не может быть изменено, все же я все еще обязан добавлять метод "разметки" к своему классу так, чтобы мое требование свойства для не возврата пустого указателя могло быть доказано:
[ContractInvariantMethod]
private void ObjectInvariant()
{
Contract.Invariant(_obj != null);
}
Существует больше, но я полагал, что, вероятно, разглагольствовал достаточно, и я был бы очень признателен за понимание людей, намного более умных, чем я, чтобы помочь мне "как" Контракты Кода и заставить это чувство помехи кода уйти. Любое понимание о том, как лучше структурировать код, обходное решение wonkiness проблемы и т.д., значительно ценилось бы.
Спасибо!
Мне это кажется такой путаницей, я бы хотел, чтобы был более чистый способ документирования требований, либо с помощью атрибутов, либо какой-либо формы встроенной поддержки языка.
Команда CC заявила, что использование атрибутов недостаточно мощно, потому что в них нельзя включать такие вещи, как лямбды. Они могут включать такие вещи, как [NotNull]
, но решили этого не делать , потому что они пытаются сохранить CC как можно более общими.
Одна из причин того, что CC является библиотекой (а не частью расширенного C #), заключается в том, что она поддерживается на всех языках .NET.
Подробнее о рассуждениях команды можно прочитать здесь .
Что касается фактического использования этого, до сих пор я просто хранил свои интерфейсные контракты в том же файле, что и интерфейс, что означает, что все это задокументировано в одном месте. Это то, что следует улучшить:)
Раздувание кода [...]
Ваша вторая жалоба, вероятно, может быть реализована - я бы посоветовал опубликовать ее на форуме контрактов кода . (РЕДАКТИРОВАТЬ: Кажется, у кого-то уже есть , но пока нет ответов.)
Однако всегда будет так, что недоопределенные контракты будут нуждаться в дополнительных предположениях, связанных с ними. Если вы столкнетесь с подобным случаем в среде .NET, вы можете запросить добавление контрактов в поток Отсутствующие контракты в библиотеках .
Кроме того, рассмотрим случай, подобный [...]
Этот был рассмотрен . Если у вас есть автоматическое свойство, вам просто нужно добавить ненулевой инвариант, и будут сгенерированы предварительные / постусловия:
public class MyClass : IInterface
{
private Object Property { get; set; }
[ContractInvariantMethod]
private void Invariants()
{
Contract.Invariant(Property != null);
}
}
Вы, вероятно, все равно получите другие инварианты для ваших классов, так что это не это большое дело.
Да, контракты загромождают все, но я чувствую, что это того стоит, когда PEX читает контракты кода и генерирует для меня 25 модульных тестов и 100% покрытие кода. Если вы еще не используете PEX, то вы упускаете самое большое преимущество кодовых контрактов. Вот начальное руководство по использованию PEX с контрактами.
Если вас беспокоит разрастание кода, то я бы посоветовал вам рассмотреть Аспектно-ориентированное программирование в качестве альтернативы.
Это позволит вам определить контракт в аспекте, а затем обернуть любые классы или методы классов в контракт. Если ваш контракт будет использоваться в более чем одном классе, то это будет более чистый способ реализации.
К сожалению, поддержка AOP в C# не очень велика, но есть несколько библиотек, которые добавляют эту функциональность.
И поскольку у меня такое же чувство к другим реализациям C#, как у вас к Contracts, то сначала все кажется прекрасным, пока вы не поднимите капюшон и не начнете использовать его всерьез.
На самом деле, я нахожу реализацию интерфейса отличной. Она не загромождает ни интерфейс, ни обычный код, и вы можете аккуратно хранить ее в какой-нибудь папке вашего решения, или в том же файле класса, что и интерфейс, в зависимости от того, что вам больше нравится.
Почему вы обязаны добавлять инвариант объекта? От вас требуется добавить столько, сколько вы считаете нужным.
Я понимаю, что вы говорите о раздутости кода, но я полагаю, что это компромисс с Code Contracts. Дополнительные проверки/безопасность означают дополнительные коды, по крайней мере, как это реализовано сейчас. Я бы постарался оставить как можно больше контрактов на интерфейсах, это должно по крайней мере помочь вам облегчить боль от разрастания кода.
Я создал плагин для Visual Studio 2010, который может сэкономить вам время на создании кодовых контрактов для интерфейсов и абстрактных классов: http://visualstudiogallery.msdn.microsoft.com/en-us/d491911d-97f3-4cf6-87b0-6a2882120acf