代码合同最佳实践

时间:2011-12-05 15:21:15

标签: c# contracts

我对代码合同以及使用它们的最佳做法有几个问题。假设我们有一个类,有几个属性(例如见下文):

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不是必填字段,所以它应该有合同。在property2上放置合同是否表明它实际上是对象的正常使用所必需的;

  2. 即使没有明确要求property2,也没有可能的原因使它为null,因此在setter处定义了合约。不定义property2上的契约会减少调用代码中的空值检查吗?这应该可以减少错误并提高代码的可维护性 - 这个假设是正确的吗?

  3. 如果它是正确的,我如何确保调用property2永远不会为null的代码?我是否使用Contract.Invariant(property2!= null);或者在构造函数中的Contract.Ensures(property2!= null),或者在setter中的Init()或Contract.Ensures(property!= null)中的Contract.Ensures(property2!= null)? (即如果使用Contract.Ensures(property2!= null),它放在哪里)?

  4. 如果问题看似简单,我道歉。我只是在寻找关于此事的想法,以及你们认为最好的做法。

4 个答案:

答案 0 :(得分:3)

就合同而言,这就是我的建议:

    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);
        }
    }

属性具有公共行为合同,并且Invariants将捕获您稍后可能引入的任何错误,因为您向Class1添加了可能修改字段值并因此违反公共合同的逻辑。或者,如果字段可以只读(并且删除了setter),则不需要不变量。

答案 1 :(得分:2)

我认为这里有很多个人喜好,但我的2美分......

1)我会,并且可能会想要调整构造函数以将property2作为可选参数:

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) 
在getter上的

- 我已经看到VS11 CTP在工具提示中显示了定义的合同,因此能够看到这个我知道我不需要检查空值。我会将Requires保留在制定者身上。

答案 2 :(得分:1)

通常,当您在对象中有一个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;
    }
}

答案 3 :(得分:1)

1/2:如果这是您要强制执行的行为,那么在property2上签订合同是合适的,即它不应该为null,并且肯定会减少对空值进行空检查和潜在错误的需要。

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;
    } 
}

这将照顾财产保护。要完全确保该属性永远不为null,您将在构造函数中添加Contract.Requires(property1!= null)。

我知道这个答案迟了3年但对你来说可能有些用处!

来源:http://research.microsoft.com/en-us/projects/contracts/userdoc.pdf