Кодекс контракта Best Practices - PullRequest
7 голосов
/ 05 декабря 2011

У меня есть несколько вопросов, касающихся контрактов по коду и рекомендаций по их использованию.Допустим, у нас есть класс с несколькими свойствами (см., Например, ниже):

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. Должен ли я вообще беспокоиться о контрактах для property2;потому что property2 не является обязательным полем, если у него вообще есть контракт.Указывает ли размещение контракта на свойство 2, что оно действительно требуется для обычного использования объекта;

  2. Даже если свойство property 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 ]

3 голосов
/ 05 декабря 2011

Это то, что я бы рекомендовал в отношении контрактов:

    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 голосов
/ 05 декабря 2011

Я думаю, что здесь есть много личных предпочтений, но мои 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 на сеттере.

1 голос
/ 13 марта 2015

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

1 голос
/ 05 декабря 2011

Часто, когда у вас есть объект 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;
    }
}
...