我不知道“它不能且通常不能执行静态分析”的断言从何而来。断言的第一部分显然是错误的。第二个取决于您所说的“经常”是什么意思。我想说的是,它经常执行静态分析,而很少执行失败。在普通的商业应用,很少越接近从来没有。
因此,这是第一个好处:
好处1:静态分析
普通的断言和参数检查有一个缺点:它们被推迟到执行代码为止。另一方面,无论是在编码步骤还是在编译应用程序时,代码协定都在更早的层次上表现出来。您越早发现错误,修复它的成本就越低。
好处2:始终保持最新文档
代码合同还提供了一种始终是最新的文档。如果该方法的XML注释SetProductPrice(int newPrice)
告知newPrice
要优于或等于零,你可能会希望文件是最新的,但你也可能发现某人改变了方法,以便newPrice = 0
抛出ArgumentOutOfRangeException
,但从来没有改变,则相应的文档。鉴于代码合同与代码本身之间的相关性,您不会遇到文档不同步的问题。
代码契约提供的这类文档也很珍贵,通常XML注释不能很好地解释可接受的值。有多少次是我想知道是否null
或string.Empty
或者\r\n
是一个方法的授权值,以及XML注释都沉默了上!
总之,没有代码合同,很多代码如下:
我会接受一些值,但不会接受其他值,但是您必须猜测或阅读文档(如果有)。实际上,不要阅读文档:它已经过时了。只需遍历所有值,您就会看到那些使我抛出异常的值。您还必须猜测可能返回的值的范围,因为考虑到过去几年对我进行的数百次更改,即使我告诉您更多有关它们的信息,也可能并非如此。
使用代码合同,它将变成:
title参数可以是长度为0..500的非空字符串。后面的整数是一个正值,仅当字符串为空时才可以为零。最后,我将返回一个IDefinition
对象,永远不会为null。
好处3:接口合同
第三个好处是,代码契约可以授权接口。假设您有类似的内容:
public interface ICommittable
{
public ICollection<AtomicChange> PendingChanges { get; }
public void CommitChanges();
...
}
您将如何仅使用断言和异常来保证CommitChanges
仅在PendingChanges
不为空时才能调用?您如何保证PendingChanges
永远不会null
?
好处4:强制执行方法的结果
最后,第四个好处是能够Contract.Ensure
得到结果。如果编写返回整数的方法时,我想确定该值永远不会低于或等于零怎么办?包括五年后,在经历了来自许多开发人员的大量变更之后?一旦方法具有多个返回点,Assert
s就会成为维护的噩梦。
考虑将代码约定不仅作为代码正确性的一种手段,而且还应作为一种更严格的代码编写方式。以类似的方式,专门使用动态语言的人可能会问为什么要在语言级别上强制类型,而在需要时可以在断言中执行相同的操作。您可以,但是与一堆断言相比,静态类型更易于使用,出错率更低,并且具有自我记录功能。
动态类型和静态类型之间的差异非常接近普通编程和按合同编程之间的差异。