代码契约最佳实践

本文关键字:最佳 契约 代码 | 更新日期: 2023-09-27 18:16:46

我有几个关于代码契约和使用它们的最佳实践的问题。假设我们有一个类,它有几个属性(如下所示):

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. 在property2上放置合同是否表明它实际上是该对象正常使用所必需的?
  3. 即使property2不是明确要求的,也没有可能的理由使它为空,因此在setter处定义的契约。在property2上定义契约不会减少调用代码中的空检查吗?这将减少bug并提高代码的可维护性——这个假设正确吗?

  4. 如果它是正确的,我如何确保调用代码property2永远不会为空?我使用契约吗?不变量(property2 != null);或合同。确保构造函数或契约中的(property2 != null)。确保Init()或Contract中的(property2 != null)。确保setter中的(property != null) ?(即,如果使用合同。确保(property2 != null),它放在哪里?

如果问题看起来很简单,我很抱歉。我只是想听听你们对这个问题的看法,以及你们认为的最佳实践。

代码契约最佳实践

这是我对合同的建议:

    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添加可能修改字段值从而违反公共契约的逻辑时,不变量将捕获您可能引入的任何错误。另外,如果字段可以设置为只读(并删除setter),则不需要不变量。

我认为这里有很多个人偏好,但我的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在定义的工具提示中显示了契约,所以能够看到这一点,我就知道我不需要检查null。我会把Requires放在setter上

通常当你在一个对象中有一个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上有一个合约是合适的,即它不应该是null,并且肯定会减少对null检查的需求和围绕null的潜在bug。

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

这将照顾到财产保护。为了完全确保属性永远不会为空,您将添加一个契约。在构造函数中要求(property1 != null)。

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

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