我最近阅读了一篇文章,将“按合同设计”与“测试驱动开发”进行了比较。似乎有很多重叠,一些冗余,以及DbC和TDD之间的一点协同作用。例如,有一些系统可以根据合同自动生成测试。
DbC以什么方式与现代类型系统重叠(例如在haskell或其中一种依赖类型的语言中)并且是否存在使用两者的优点?
答案 0 :(得分:16)
Ralf Hinze,Johan Jeuring和AndresLöh撰写的“功能编程类型合同”有一个方便的表格,说明行踪合同在“检查”的设计范围内:
| static checking | dynamic checking
-------------------------------------------------------------------
simple properties | static type checking | dynamic type checking
complex properties | theorem proving | contract checking
见这里:
答案 1 :(得分:7)
似乎大多数答案都假设合同是动态检查。 请注意,在某些系统中,合同是静态检查。在 在这样的系统中,你可以将合同视为一种限制形式 依赖类型,可以自动检查。对比这个 具有更丰富的依赖类型,以交互方式进行检查,例如 在Coq。
请参阅Dana Xu's page上的“规格检查”部分 关于静态和混合检查(静态跟随动态)的论文 Haskell和OCaml的合同。徐的合同制度包括 细化类型和相关箭头,两者都是相关的 类型。早期语言具有受限制的依赖类型 自动检查包括Pfenning和Xi的DML和ATS。 在DML中,与Xu的工作不同,依赖类型是受限制的 自动检查已完成。
答案 2 :(得分:5)
主要区别在于测试是动态的和不完整的,依靠测量来证明您已经完全验证了您正在测试的任何属性,而类型和类型检查是一个正式的系统,可以保证所有可能的代码路径都经过验证。你所说的财产类型。
对属性的测试只能接受限制,即对同一属性的类型检查提供开箱即用的保证级别。合同增加了动态检查的基线。
答案 3 :(得分:4)
DBC是有价值的,只要你不能表达systen类型本身的所有假设。简单的haskell示例:
take n [] = []
take 0 _ = []
take n (a:as) = take (n-1) as
类型为:
take :: Int -> [a] -> [a]
然而,只有大于等于0的值才适用于n。这是DBC可以介入的地方,例如,生成适当的quickcheck属性。
(当然,在这种情况下,也很容易检查负值并修复未定义的结果 - 但是有更复杂的情况。)
答案 4 :(得分:0)
我认为DbC和Type的系统不可比。 DbC和类型系统(甚至验证系统)之间存在混淆。例如,我们可以比较DbC和Liquid Haskell工具或DbC和QuickCheck框架。恕我直言,这是不正确的:类型系统和形式证明系统仅主张一个-您:您在脑海中拥有一些算法,并且声明了这些算法的属性。然后,您将实现这些算法。类型系统以及正式的证明系统会验证实现代码是否与声明的属性相对应。
DbC不验证内部事物(实现/代码/算法),而是验证外部事物:代码外部事物的预期功能。它可以是环境状态,文件系统状态,DB状态,调用方状态,可以确定您自己的状态。典型的合同在运行时工作,而不是在编译时或在特殊的验证阶段。
DbC的规范示例显示了如何在HP芯片中发现错误。发生这种情况是因为DbC声明了外部组件的属性:芯片的状态,状态,转换等。并且,如果您的应用程序遇到意外的外部世界状态,它将报告这种情况为异常。这里的魔术是:1)您可以在一处定义合同,而不必重复自己; 2)合同很容易关闭(从已编译的二进制文件中删除)。它们更像Aspects IMHO,因为它们不像子例程调用那样“线性”。
我在这里的回顾是,DbC比类型系统更有助于避免实际错误,因为大多数实际错误是由于对外部世界/环境/框架/ OS组件/行为的误解而发生的。类型和证明帮助仅有助于避免您自己的简单错误,这些错误可以在测试或建模中找到(在MatLab,Mathematica等中)。
简而言之:您无法在系统类型为HP的芯片中找到错误。当然,存在一种幻觉,即可能发生索引式monad之类的事情,但是经过这种尝试的真实代码看起来超级复杂,无法支持且不切实际。但是我认为可能有一些混合方案。