由合同和构造者设计

我正在为学校目的实现我自己的ArrayList,但为了调整一些东西,我正在尝试使用C#4.0代码契约。 一切都很好,直到我需要将Contracts添加到构造函数中。 我应该在空参数构造函数中添加Contract.Ensures()吗?

public ArrayList(int capacity) { Contract.Requires(capacity > 0); Contract.Ensures(Size == capacity); _array = new T[capacity]; } public ArrayList() : this(32) { Contract.Ensures(Size == 32); } 

我会说是的,每种方法都应该有明确的合同。 另一方面,如果只是将工作委托给“主”构造函数,为什么要这样做呢? 逻辑上,我不需要。

我认为在两个构造函数中明确定义合同有用的唯一方面是,如果将来我们对合同有Intelisense支持。 如果发生这种情况,那么明确每种方法所具有的合同是非常有用的,因为它出现在Intelisense中。

此外,是否有任何书籍更深入地了解合同设计的原则和用法? 有一件事是知道如何在一种语言中使用Contracts的语法(在本例中为C#),另一种是知道如何以及何时使用它。 我阅读了几篇教程和Jon Skeet关于它的C#深度文章,但如果可能的话,我想更深入一些。

谢谢

我完全不同意托马斯的回答。 只要您在ArrayList()的实现中做出选择,您就应该签订一份合同来记录这些选择。

在这里,您可以选择使用参数32调用主构造函数。还有许多其他事情可以决定(不仅仅是关于默认大小的选择)。 给ArrayList()提供一个几乎与ArrayList(int)文档完全相同的契约,你决定不做大部分愚蠢的事情,而不是直接调用它。

答案“它调用主构造函数,所以让主构造函数的契约完成工作”完全忽略了这样一个事实,即合同可以帮助您避免查看实现。 对于基于运行时断言检查的validation策略,即使对于几乎直接调用另一个构造函数/方法的这种简短构造函数/方法,编写契约的缺点是最终会检查两次。 是的,这似乎是多余的,但运行时断言检查只是一种validation策略,而DbC的原则是独立的。 原则是:如果可以调用它,它需要一份合同来记录它的作用。

使用ArrayList客户端代码(使用代码约定)将不知道空构造函数Ensure Size == 32除非您使用Ensure明确说明。

所以(例如):

 var x = new ArrayList(); Contract.Assert(x.Size == 32) 

会给你警告“断言没有certificate”。

你需要明确说明所有合同; 代码合同重写器/静态检查器不会“查看”方法以查看任何含义 – 请参阅我对相关问题的回答“我们是否必须在委派方法中冗余地指定Contract.Requires(…)语句?”

我建议阅读Bertrand Meyer的面向对象软件构建,第2版或者Touch of Class 。 或者,您可以阅读同一作者的1992年文章“合同设计”

总结一下:

  • 在构造函数(其中任何一个)完成之后,以及在执行类的任何公共方法之前和之后, 类不变量必须保持。
  • 方法前置条件后置条件是进入和退出任何公共方法以及不变量时必须遵守的附加条件。

所以在你的情况下,专注于不变量。 无论调用哪个构造函数,都会生成一个正确的对象(一个满足类不变量的对象)。

在这个相关的答案中,我讨论了类似的主题,包括一个例子

嗯,我不完全理解为什么你把’Ensures’也放在默认的c’tor中。 因为它调用了主要的c’tor,它已经实现了完整的合同,默认的c’tor也是如此 – 按照定义。 所以这是一个逻辑冗余,因此是一个很大的“不要”。 也许它可能具有实用意义,就像你说的那样 – 不知道代码合同那么好……

关于文学 – 最好的来源是:

  • 按合同设计,例如(Book,Addison-Wesley)
  • 关于这个问题的维基百科文章
  • 合同设计简介 (Eiffel系列,但DbC原则不是针对特定语言的)

HTH! 托马斯

合同设计来自函数式编程的数学根源: 前置条件和后置 条件 。

你真的不需要一本书,它最多只是计算机科学学位的一章(大多数人都会教这个概念)。 基本前提是您编写函数所需的前提条件以及在给定正确参数的情况下将生成的输出。 预计该函数不会使用不正确的初始参数。 对于算法也可以这样说:它是绝对可靠的,即它保证提供预期的结果。

这就是我在目前正在学习的程度上的教学方式,但可能有更好的定义。 关于按合同设计的维基百科文章是用OO倾斜编写的,但前/后条件与语言无关。