为什么在大多数现代编程语言中对合同设计的支持如此有限?


40

我最近发现了按合同设计(DbC),发现这是一种非常有趣的编写代码的方式。除其他外,它似乎提供:

  • 更好的文档。由于合同是文件,因此不可能过时。此外,由于合同明确规定了例程的功能,因此有助于支持重用。
  • 更简单的调试。由于程序在合同失效时立即停止执行,因此错误不会传播,并且可能会突出显示违反的特定断言。这在开发和维护期间提供了支持。
  • 更好的静态分析。DbC基本上只是Hoare逻辑的一种实现,应该应用相同的原理。

相比之下,成本似乎很小:

  • 额外的手指打字。由于必须明确说明合同。
  • 进行一些培训以使自己习惯于写合同。

现在,首先熟悉Python,我意识到实际上可以编写前提条件(只针对不适当的输入抛出异常),甚至可以使用断言再次测试某些条件。但是,如果没有一些最终被认为不是Python风格的额外魔术,就不可能模拟“旧”或“结果”之类的某些功能。(此外,有一些库提供支持,但最终我感觉使用它们是错误的,因为大多数开发人员没有这样做。)我假设所有其他语言都存在类似的问题(当然,除外) ,埃菲尔)。

我的直觉告诉我,缺乏支持一定是由于某种拒绝做法的结果,但是在线搜索并没有取得丰硕的成果。我想知道是否有人可以弄清楚为什么大多数现代语言似乎提供的支持很少?DbC有缺陷还是过于昂贵?还是由于极限编程和其他方法而过时?


听起来这是执行测试驱动的编程的一种过于复杂的方法,但却没有对程序进行测试的好处。

3
@Dan,不是,我认为它更多地是类型系统的扩展。例如,一个函数不仅接受整数参数,还接受整数,根据合同规定,该整数必须大于零
Carson63000'1

4
@Dan代码合同显着减少了您需要执行的测试数量。
宫阪丽

24
@丹,我宁愿说TDD是穷人的合同,反之亦然。
SK-logic

在动态语言中,您可以基于可选标志用合同“装饰”对象。我有一个示例实现,该实现使用环境标志来选择猴子通过合同猴子修补现有对象。是的,该支持不是本地的,但是很容易添加。测试工具也是如此,它们不是本机的,但易于添加/编写。
雷诺斯2012年

Answers:


9

可以说,几乎每种编程语言都支持它们。

您需要的是“断言”。

这些很容易被编码为“ if”语句:

if (!assertion) then AssertionFailure();

这样,您可以通过将这样的断言放在代码的顶部来限制输入,从而编写合同。返回点的输出约束。您甚至可以在整个代码中添加不变式(尽管实际上并不是“按合同设计”的一部分)。

因此,我认为它们之所以没有普及,是因为程序员懒于编写代码,而不是因为你做不到。

通过定义编译时布尔常量“检查”并稍微修改一下语句,可以使它们在大多数语言中更有效率。

if (checking & !Assertion) then AssertionFailure();

如果您不喜欢语法,则可以使用各种语言抽象技术,例如宏。

一些现代语言为此提供了很好的语法,这就是我认为“现代语言支持”的意思。那是支持,但是很薄。

即使是现代语言,大多数语言也不能给您带来“时间”断言(在任意先前或之后的状态(“时间最终”最终“),如果您想编写非常有趣的合同,则需要使用它。IF语句将无济于事你在这里。


我发现只能访问断言的问题是,没有有效的方法来检查命令中的后置条件,因为您经常需要将后置条件状态与前置条件状态进行比较(埃菲尔称其为“旧”并自动将其传递至后置条件例程) 。)在Python中,可以使用装饰器轻松地重新创建此功能,但是在需要关闭断言时,它会显得很短。
Ceasar Bautista 2012年

埃菲尔实际上节省了多少以前的状态?由于它合理地知道不解决暂停问题(通过分析功能)就可以访问/修改哪个部分,因此它要么必须保存完整的机器状态,要么只是保存一部分非常浅的部分。我怀疑是后者;前提条件之前,这些可以通过简单的标量分配“模拟”。我很高兴得知埃菲尔铁塔没有这样做。
艾拉·巴克斯特

7
...只是检查了埃菲尔铁塔的工作方式。“ old <exp>”是该函数入口处的<exp>的值,因此正如我预期的那样,它正在函数入口处进行浅表复制。您也可以做到。我同意让编译器实现pre / post / old的语法比手工完成所有操作更方便,但是重点是可以手工完成,这确实不难。我们回到了懒惰的程序员那里。
艾拉·巴克斯特

@IraBaxter编号。如果可以将合同与实际逻辑分开,则代码将变得更简单。此外,如果编译器可以告诉合同和代码分开,它可以通过减少重复了很多。例如,在D中,您可以在接口或超类上声明协定,并且断言将应用于所有实现/扩展类,无论其功能中的代码如何。例如,使用python或Java,super如果只希望检查合同而不重复,则必须调用整个方法,并可能丢弃结果。这确实有助于实现干净的LSP兼容代码。
marstato

@marstato:我已经同意在语言方面的支持是一件好事。
伊拉·巴克斯特

15

正如您所说,“按合同设计” Eiffel的一项功能,长期以来,它一直是社区中备受推崇的编程语言之一,但从未流行。

DbC并不是使用任何一种最流行的语言,因为直到最近,主流编程社区才开始接受对程序员的代码添加约束/期望是程序员期望的“合理”事情。对于程序员来说,现在很常见的是理解单元测试的价值,而这正逐渐渗入到程序员对接受代码以验证其参数并看到收益的接受中。但是十年前,可能大多数程序员都会说:“对于您所知道的总会没事的东西来说,这只是额外的工作。”

我认为,如果您今天要去找普通的开发人员讨论后置条件,他们会热情地点头并说:“好吧,这就像单元测试一样。” 而且,如果您谈论前提条件,他们会说:“好吧,这就像参数验证一样,我们并不总是这样做,但是,您知道,我想那是可以的……”然后,如果您谈论不变式,他们会开始说:“天哪,这会带来多少开销?我们还要捕捉多少个bug?” 等等

因此,我认为要广泛采用DbC,还有很长的路要走。


OTOH,主流程序员已经习惯了一段时间来编写断言了。在大多数现代主流语言中缺少可用的预处理器,使这种好的做法效率低下,但是对于C和C ++仍然很常见。现在,它与Microsoft代码合同(基于AFAIK,该版本的字节码重写)卷土重来。
SK-logic

8

我的直觉告诉我,缺乏支持一定是由于某种拒绝实践的结果,...

假。

这是一种设计实践。它可以用代码(埃菲尔风格)显式体现,也可以用代码(大多数语言)或单元测试隐式体现。设计实践存在并且运作良好。语言支持遍及整个地图。但是,它在单元测试框架中以多种语言提供。

我想知道是否有人可以弄清楚为什么大多数现代语言似乎提供的支持很少?DbC有缺陷还是过于昂贵?

它的价格昂贵。和。更重要的是,有一些事情不能证明一个给定的语言。例如,循环终止不能用编程语言来证明,它需要“高阶”证明能力。因此,某些合同在技术上是无法表达的。

还是由于极限编程和其他方法而过时?

没有。

我们主要使用单元测试来证明DbC已实现。

正如您所指出的,对于Python,DbC有多个位置。

  1. docstring和docstring测试结果。

  2. 确认输入和输出的断言。

  3. 单元测试。

进一步。

您可以采用有经验的编程风格的工具,以便编写包含DbC信息的文档,并生成干净的Python和单元测试脚本。熟练的编程方法使您可以撰写精美的文献,其中包括合同和完整的资料。


您可以证明循环终止的琐碎情况,例如在固定有限序列上进行迭代。这是广义的循环,无法证明它会轻易终止(因为它可能正在寻找“有趣的”数学猜想的解决方案);这就是暂停问题的全部实质。
Donal Fellows 2012年

+1。我认为您是唯一涵盖最关键点的人there are some things which cannot be proven。正式验证可能很棒,但并非一切都可验证!因此,该功能实际上限制了编程语言的实际作用!
Dipan Mehta 2012年

@DonalFellows:由于无法证明一般情况,因此很难合并(a)昂贵且(b)不完整的一系列功能。我的回答是,在存在限制的情况下,更容易避开所有这些功能并避免对形式正确性证明设置虚假的期望。作为设计练习(在语言之外),可以(并且应该)使用许多证明技术。
S.Lott 2012年

对于像C ++这样的合同检查在发布版本中进行编译的语言来说,这一点都不昂贵。使用DBC往往会导致发布更轻量的构建代码,因为对合法状态的程序进行了更少的运行时检查。我已经看不到令人难以置信的代码库的数量了,我已经看到许多函数会检查非法状态并返回false,而在经过适当测试的发行版本中,绝不应将其置于该状态。
Kaitain

6

只是猜测。不太流行的部分原因可能是因为“按合同设计”是Eiffel的商标。


3

一种假设是,对于一个足够大的复杂程序,尤其是目标移动的程序,合同本身的数量可能会比单独的程序代码变得多虫且难以调试,甚至更多。与任何模式一样,在收益递减的情况下,很可能会有用法,而当以更有针对性的方式使用时,则具有明显的优势。

另一个可能的结论是,“托管语言”的流行是对那些选定的托管功能(按合同划分的数组边界等)按合同设计支持的最新证明。


>与我从未见过的程序代码相比,大量合同本身可能会变得像虫子一样难以调试,甚至更多。
Kaitain

2

大多数主流语言在语言中没有DbC功能的原因是实现它的成本效益比对于语言实现者而言是很高的。

在其他答案中已经介绍了这一方面,单元测试和其他运行时机制(甚至某些带有模板元编程的编译时机制)可以为您带来许多DbC好处。因此,尽管有一个好处,但它可能被认为是相当适度的。

另一方面是成本,将DbC改造为现有语言可能会带来很大的突破性变化,并且启动起来非常复杂。在不破坏旧代码的情况下以一种语言引入新语法是很困难的。更新现有的标准库以使用如此深远的更改将非常昂贵。因此,我们可以得出结论,以现有语言实现DbC功能具有很高的成本。

我还要指出,与模板差不多的合同,因此在某种程度上与DbC有关的概念,已从最新的C ++标准中删除,因为即使经过多年的努力,估计它们仍需要多年的工作。这些对语言的巨大,广泛而广泛的更改实在太难实现了。


2

如果可以在编译时检查合同,则将更广泛地使用DbC,这样就不可能运行违反任何合同的程序。

如果没有编译器支持,“ DbC”只是“检查不变量/假设并在违反时抛出异常”的花药名称。


这不会遇到停顿的问题吗?
Ceasar Bautista 2012年

@Ceasar这取决于。一些假设可以检查,而其他则不能。例如,有些类型系统可以避免传递空列表作为参数或返回一个列表。
Ingo 2012年

不错的观点(+1)尽管Bertrand Meyer在“编程大师”部分中也提到了他们的随机类创建系统,并要求检查是否违反合同。因此,这是一种混合的编译时/运行时方法,但是我怀疑这种技术在每种情况下都有效
Maksee 2013年

在某种程度上是正确的,尽管这应该是灾难性的失败,而不是例外(请参阅下文)。DBC的主要优势在于方法论,这实际上导致了程序设计的改善,并且保证了任何给定的方法在进入时都必须处于合法状态,从而简化了许多内部逻辑。通常,在违反合同时,您不应该进行异常处理。如果程序可以合法地处于〜X状态,则应使用异常,并且客户端代码必须对此进行处理。合同会说〜X完全是非法的。
Kaitain

1

我的解释很简单,除非有人认为有必要,否则大多数人(包括程序员)都不需要额外的工作。在安全性非常重要的航空电子编程中,我没有看到大多数没有它的项目。

但是,如果您正在考虑进行网站,桌面或移动编程,则有时不会将崩溃和意外行为视为坏消息,并且程序员在报告错误时会避免额外的工作,而在以后修复这些错误就足够了。

这可能是我认为Ada从未涉足航空编程行业的原因,尽管Ada是一种很棒的语言,但是它需要进行更多的编码工作,并且如果您想构建一个可靠的系统,它是工作的最佳语言(不包括SPARK基于Ada的语言)。

由Microsoft进行的C#合同库设计是实验性的,它们对于构建可靠的软件非常有用,但是它们从未在市场上获得发展动力,否则您现在将它们视为C#核心语言的一部分。

断言与对前后条件和不变条件的全功能支持不同。尽管它可以尝试模拟它们,但是具有适当支持的语言/编译器会“抽象语法树”分析并检查逻辑错误,而断言根本无法做到。

编辑:我进行了搜索,以下是相关的讨论可能对您有帮助:https : //stackoverflow.com/questions/4065001/are-there-any-provable-real-world-languages-scala


-2

主要原因如下:

  1. 仅提供不流行的语言
  2. 这是没有必要的,因为实际上任何人都可以用现有的编程语言以相同的方式完成相同的工作
  3. 很难理解和使用-它需要专业知识才能正确执行,因此很少有人在做
  4. 这样做需要大量代码-程序员更喜欢尽量减少编写的字符数量-如果需要很长的代码,则一定有问题
  5. 优势不存在-只是找不到足够的bug使其值得

1
您的回答没有很好的论据。您只是在说自己的观点,认为没有优势,它需要大量代码,并且没有必要,因为可以使用现有语言来完成(OP专门解决了此问题!)。
Andres F. 2012年

我没有考虑断言等替代它。这不是在现有语言中实现的正确方法。(即,尚未解决)
tp1 2012年

@ tp1,如果程序员真的想要最小化类型,他们将永远不会陷入像Java这样冗长而雄辩的事情。是的,编程本身需要“专业知识”才能“正确地做到”。那些不具备这种知识的人不应该被允许编码。
SK-logic

@ Sk-logic:好吧,似乎世界上有一半人在做OO错误,仅仅是因为他们不想将转发功能从成员函数写入数据成员的成员函数。根据我的经验,这是一个大问题。这是由于最小化要写入的字符数直接引起的。
tp1 2012年

@ tp1,如果人们真的想最小化键入,他们甚至都不会碰到OO。OOP自然是雄辩的,即使是在其最佳实现中,例如Smalltalk。我不会说这是不好的财产,口才有时会有所帮助。
SK-logic
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.