代码合约最佳实践

7

我有几个关于代码合同及其使用最佳实践的问题。假设我们有一个类,其中包含几个属性(如下所示):

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不是明确需要的,但是没有任何理由使其为空,因此在setter中定义了合同。 在property2上定义契约是否会减少调用代码中的空检查? 这应该减少错误并提高代码的可维护性-这种假设是否正确?

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

如果问题似乎很简单,请原谅。 我只是想听听您的想法和您认为的最佳实践。


基于你列出的原因,我同意关于财产2的合同。 - Daniel Hilgarth
4个回答

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

这些属性有公共的行为契约,不变式将捕捉您在向Class1添加可能修改字段值并因此违反公共协议的逻辑时可能引入的任何错误。或者,如果可以将字段设置为只读(并删除setter),则不需要不变式。


我同意在设置器(setters)中使用ce。在阅读代码合同示例的文档后,开发人员似乎就是这样打算的。而且,在变量不变式(invariants)中捕获违规行为是可以的。谢谢你,你真的帮助我理解了这个问题。 - Red Rubicon

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 值。对于 Setter,我会保留 Requires


1

1/2: 如果您想要执行的行为是不允许 null 值,那么在 property2 上签订合同是适当的,并且肯定会减少对 null 值检查和潜在的 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;
    } 
}

那样会保护属性。为了完全确保该属性永远不会为空,您将在构造函数中添加Contract.Requires(property1!= null)。
我知道这个答案已经晚了3年,但它可能对您有所帮助!
来源:http://research.microsoft.com/en-us/projects/contracts/userdoc.pdf

1
通常当您在对象中拥有一个列表时,您将让对象负责创建。因此,如果消费者希望添加到列表中,他们必须先获取它。根据该类的使用情况,这可能是更好的方法。
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;
    }
}

感谢您提供有关列表方面的信息。这是我关注的问题之一,这就是为什么我将其作为非必需属性包含在内的原因。我主要是想寻求有关如何处理“非必要”属性的合同建议,无论它是否为列表。有任何意见吗? - Red Rubicon
在不是 Collection 的情况下,我会遵循 Smudge 的建议。通过在构造函数中创建一个默认值并要求其不能为空,它告诉 API 的消费者,如果他们提供了一个值,那么应该提供一个非空值。 - Daniel Moses

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接