代码契约最佳实践
本文关键字:最佳 契约 代码 | 更新日期: 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不是显式必需的。
我有以下问题:
我应该为property2的合同操心吗?因为property2不是必填字段,它应该有一个契约吗?
在property2上放置合同是否表明它实际上是该对象正常使用所必需的?即使property2不是明确要求的,也没有可能的理由使它为空,因此在setter处定义的契约。在property2上定义契约不会减少调用代码中的空检查吗?这将减少bug并提高代码的可维护性——这个假设正确吗?
如果它是正确的,我如何确保调用代码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