Кодекс контракта Best Practices
У меня есть несколько вопросов, касающихся контрактных кодов и рекомендаций по их использованию. Допустим, у нас есть класс с несколькими свойствами (см., Например, ниже):
class Class1
{
// Fields
private string _property1; //Required for usage
private List<object> _property2; //Not required for usage
// Properties
public string Property1
{
get
{
return this._property1;
}
set
{
Contract.Requires(value != null);
this._property1 = value;
}
}
public List<object> Property2
{
get
{
return this._property2;
}
set
{
Contract.Requires(value != null);
this._property2 = value;
}
}
public Class1(string property1, List<object> property2)
{
Contract.Requires(property1 != null);
Contract.Requires(property2 != null);
this.Property1 = property1;
this.Property2 = property2;
}
public Class1(string property1)
: this(property1, new List<object>())
{ }
}
Некоторое объяснение того, чего я хочу достичь:
(a) property1 - обязательное поле. property2 явно не требуется для нормального использования объекта.
У меня есть следующие вопросы:
Должен ли я даже возиться с контрактами на недвижимость2; потому что property2 не является обязательным полем, если у него вообще есть контракт. Указывает ли размещение контракта на имущество2, что оно действительно требуется для нормального использования объекта;
Несмотря на то, что свойство 2 явно не требуется, нет никаких причин для того, чтобы оно было нулевым, таким образом, определенный контракт в установщике. Разве определение контракта для property2 не уменьшит проверки нуля в вызывающем коде? Это должно уменьшить количество ошибок и улучшить удобство сопровождения кода - верно ли это предположение?
Если это правильно, как я могу гарантировать, что при вызове кода свойство property2 никогда не будет нулевым? Я использую Contract.Invariant(property2!= Null); или Contract.Ensures(property2!= null) в конструкторе, или Contract.Ensures(property2!= null) в Init(), или Contract.Ensures(property!= null) в установщике? (т.е. если вы используете Contract.Ensures(property2!= null), где он находится)?
Мои извинения, если вопросы кажутся простыми. Я просто ищу мысли по этому вопросу и то, что вы, ребята, считаете лучшей практикой.
4 ответа
Вот что я бы порекомендовал в отношении контрактов:
class Class1
{
// Fields
private string _property1; //Required for usage
private List<object> _property2; //Not required for usage
// Properties
public string Property1
{
get
{
Contract.Ensures(Contract.Result<string>() != null);
return this._property1;
}
set
{
Contract.Requires(value != null);
this._property1 = value;
}
}
public List<object> Property2
{
get
{
Contract.Ensures(Contract.Result<List<object>>() != null);
return this._property2;
}
set
{
Contract.Requires(value != null);
this._property2 = value;
}
}
public Class1(string property1, List<object> property2)
{
Contract.Requires(property1 != null);
Contract.Requires(property2 != null);
this.Property1 = property1;
this.Property2 = property2;
}
public Class1(string property1)
: this(property1, new List<object>())
{
Contract.Requires(property1 != null);
}
[ContractInvariantMethod]
private void ContractInvariants()
{
Contract.Invariant(_property1 != null);
Contract.Invariant(_property2 != null);
}
}
Свойства имеют свои общедоступные поведенческие контракты, и инварианты будут обнаруживать любые ошибки, которые вы можете внести позже, когда вы добавляете логику в Class1, которая может изменять значения полей и таким образом нарушать публичные контракты. С другой стороны, если поля могут быть сделаны только для чтения (и сеттеры удалены), вам не нужны инварианты.
Я думаю, что здесь есть много личных предпочтений, но мои 2 цента...
1) Я бы и, возможно, испытал бы соблазн настроить конструкторы так, чтобы они имели свойство2 в качестве необязательного аргумента:
Class1(string property1, List<object> property2 = new List<object>())
{
Contract.Requires(property1 != null);
Contract.Requires(property2 != null);
this.Property1 = property1;
this.Property2 = property2;
}
2) см 3
3) Прежде чем я буду счастлив уменьшить количество пустых проверок в коде вызова, я бы лично предпочел увидеть
Contract.Ensures(property2 != null)
на геттере - я видел, как VS11 CTP показывает контракты во всплывающих подсказках для определений, поэтому, когда я смог это увидеть, я бы знал, что мне не нужно проверять наличие нулей. Я бы сохранил Requires
на сеттер.
Часто, когда у вас есть List в объекте, он будет заботиться о создании объекта. Поэтому, если потребитель хочет добавить в список, он должен сначала получить его. В зависимости от использования этого класса это может быть лучший маршрут.
class Class1
{
// Fields
private string _property1; //Required for usage
private List<object> _property2 = new List<object>(); //Not required for usage
// Properties
public string Property1
{
get
{
return this._property1;
}
set
{
Contract.Requires(value != null);
this._property1 = value;
}
}
public List<object> Property2
{
get
{
return this._property2;
}
//We don't have a setter.
}
public Class1(string property1)
{
Contract.Requires(property1 != null);
this.Property1 = property1;
}
}
1/2: Наличие контракта на property2 уместно, если это поведение, которое вы хотите применить, т.е. оно не должно быть нулевым, и, безусловно, уменьшит необходимость проверки на ноль и потенциальных ошибок вокруг нулей.
3: чтобы ответить на этот вопрос, я переписал ваш класс следующим образом
class Class1
{
//Properties
public string Property1 { get; set; }
public List<object> Property2 { get; set; }
public Class1(string property1, List<object> property2)
{
Contract.Requires(property1 != null);
Contract.Requires(property2 != null);
Property1 = property1;
Property2 = property2;
}
public Class1(string property1)
: this(property1, new List<object>())
{
Contract.Requires(property1 != null);
}
[ContractInvariantMethod]
private void ObjectInvariant()
{
Contract.Invariant(Property1 != null);
Contract.Invariant(Property2 != null);
}
}
Если у вас есть автоматически реализованные свойства в вашем классе, вы можете использовать Contract.Invariant()
, Это делает явные контракты в вашем свойстве избыточными, поэтому нет необходимости в следующем коде.
public string Property1
{
get
{
Contract.Ensures(Contract.Result<string>() != null);
return this._property1;
}
set
{
Contract.Requires(value != null);
this._property1 = value;
}
}
Это позаботится о защите имущества. Чтобы полностью гарантировать, что свойство никогда не будет нулевым, вы затем добавите Contract.Requires(property1!= Null) в конструктор.
Я знаю, что этот ответ опоздал на 3 года, но он может быть вам полезен!
Источник: http://research.microsoft.com/en-us/projects/contracts/userdoc.pdf