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. 我是否应该为财产合同而烦恼2?因为 property2 不是必填字段,所以它是否应该有合同。就财产 2 签订合同是否表明该合同实际上是该物品正常使用所必需的;

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

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

如果问题看起来很简单,我很抱歉。我只是在寻找对此事的想法,以及你们认为的最佳实践。

4

4 回答 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 添加可能修改字段值并因此违反公共契约的逻辑时,不变量将捕获您稍后可能引入的任何错误。或者,如果可以将字段设置为只读(并删除设置器),则不需要不变量。

于 2011-12-05T17:24:12.090 回答
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) 

在吸气剂上 - 我已经看到 VS11 CTP 在定义的工具提示中显示合同,因此能够看到这一点,我就会知道我不需要检查空值。我会保留Requires二传手。

于 2011-12-05T16:22:35.090 回答
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;
    }
}
于 2011-12-05T16:31:05.767 回答
1

1/2:如果这是您要强制执行的行为,则在 property2 上签订合同是合适的,即它不应该为 null,并且肯定会减少对 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

于 2015-03-13T17:04:00.440 回答