Кодекс контракта 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 явно не требуется для нормального использования объекта.

У меня есть следующие вопросы:

  1. Должен ли я даже возиться с контрактами на недвижимость2; потому что property2 не является обязательным полем, если у него вообще есть контракт. Указывает ли размещение контракта на имущество2, что оно действительно требуется для нормального использования объекта;

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

  3. Если это правильно, как я могу гарантировать, что при вызове кода свойство 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

Другие вопросы по тегам