程序正确性,规格


17

来自Wikipedia:在理论计算机科学中,当说算法相对于规范是正确的时,就断言了算法的正确性。

但是问题在于,获得“适当的”规范并不是一件容易的事,并且(据我所知)没有100%正确的方法来获得正确的规范,这只是一种估计,因此,如果我们要仅仅因为谓词看起来像“一个”就以谓词为规范,为什么不仅仅因为它看起来“正确”就认为程序是正确的呢?


2
因为希望规范比程序少复杂,所以它比程序少出错。
user253751 '17

1
请注意,类型系统是规范的一种形式-我们可以使用它来证明程序的某些非平凡属性。类型系统越丰富,我们就能证明的性能越强。
gardenhead

@immibis,但如果只有一个错误,那就说明整个事情都是错误的
Maykel Jakson

@MaykelJakson是的...我曾经错误地将一个矛盾放在罗丹的一个公理中(我试图做的是正确的,但是语法是错误的)。在我注意到之前,我花了一段时间“自动检验器似乎工作异常正常”。
user253751 '17

Answers:


30

首先,您绝对正确:您真正关心的是。形式验证将对程序正确性的信心问题转移到对规范正确性的信心问题,因此这不是灵丹妙药。

但是,有几个原因使该过程仍然有用。

  1. 规范通常比代码本身更简单。例如,考虑对整数数组进行排序的问题。有相当复杂的排序算法,可以做一些聪明的事情来提高性能。但是规范很容易陈述:输出必须按升序排列,并且必须是输入的排列。因此,可以说,对规范的正确性比对代码本身的正确性更容易获得信任。

  2. 没有单点故障。假设您有一个人写下了一个规范,另一个人写下了源代码,然后正式验证该代码是否符合规范。然后,规范代码中必须存在任何未发现的缺陷。在某些情况下,对于某些类型的缺陷,这种感觉不太可能:这是不太可能的检查规范时,你会忽视的缺陷检查源代码时忽略了这个安全漏洞。不是全部,而是一些。

  3. 局部规范可能比代码简单得多。例如,考虑对程序没有缓冲区溢出漏洞的要求。或者,要求没有数组索引越界错误。这是一个简单的规范,显然可以证明是一件好事。现在,您可以尝试使用形式化方法来证明整个程序符合此规范。这可能是一项相当艰巨的任务,但是如果您成功了,您将对程序有更大的信心。

  4. 规范更改的频率可能不如代码更改的频率。没有正式的方法,每次我们更新源代码时,都必须手动检查更新是否不会引入任何错误或缺陷。形式化方法可以减轻这种负担:假设规范没有变化,因此软件更新仅涉及对代码的更改,而不涉及对规范的更改。然后,对于每次更新,您都无需检查规范是否仍然正确(它没有更改,因此不存在在规范中引入新错误的风险)和检查代码是否仍旧的负担。正确(程序验证程序会为您检查)。您仍然需要检查原始规范是否正确,但是不必在开发人员每次提交新的补丁/更新/更改时都进行检查。

最后,请记住,规范通常是声明性的,不一定要执行或直接编译为代码。例如,考虑再次排序:规范说输出正在增加并且是输入的排列,但是没有明显的方法直接“执行”该规范,也没有明显的方法让编译器自动将其编译为代码。因此,仅使规范正确并经常执行就不是一个选择。

尽管如此,底线仍然是相同的:形式化方法不是万能药。他们只是将对代码正确性的(非常困难)信任问题转换为对规范正确性的(非常困难)信任问题。规范中的错误是一个真正的风险,它们很常见,并且不容忽视。确实,形式方法社区有时会将问题分为两部分:验证是关于确保代码符合规范;验证是关于确保规格正确(满足我们的需求)。

在实践中,您也可能会喜欢正式的程序验证为什么我们不对编译时保证进行更多研究?有关此方面的更多观点。


顺便说一句,随着规范变得更加详细,可以将其编写为伪代码的可能性增加了。使用您的排序示例,“输出必须按升序排列”的更详细版本将是“输出中的每个整数,第一个后必须大于前一个数字”。反过来,这可以很容易地写为for each integer I<sub> N</ sub> in set S (where N > 1) { assert I<sub> N</ sub> > I<sub> N - 1</ sub>之类的东西}。不能100%确定该表示法。
贾斯汀时间-恢复莫妮卡

因此,好的规范还可以帮助人们创建代码,而不仅仅是验证代码。
贾斯汀时间-恢复莫妮卡

1
执行排序规范的明显方法是枚举输入的所有排列并选择有序排列。但是,与有关的问题应该很清楚……
德里克·埃尔金斯

19

DW的答案很好,但我想谈一点。规范不仅仅是验证代码所依据的参考。拥有正式规范的原因之一是通过证明一些基本属性来对其进行验证。当然,规范不能完全验证-验证将与规范本身一样复杂,因此将是一个无休止的过程。但是验证使我们可以对某些关键属性获得更强有力的保证。

例如,假设您正在设计汽车自动驾驶仪。这是一个非常复杂的事情,涉及很多参数。自动驾驶仪的正确性包括诸如“汽车不会撞在墙上”和“汽车将被告知要行驶的地方”之类的东西。诸如“汽车不会撞到墙壁”这样的属性确实非常重要,因此我们想证明这一点。由于系统在物理环境中运行,因此您需要添加一些物理约束。计算系统的实际属性将类似于“在有关材料科学的这些假设以及关于汽车传感器对障碍物的感知的这些假设下,汽车不会撞在墙上”。但是即使这样,结果仍然是一个相对简单的属性,显然是合乎需要的。

您可以从代码中证明此属性吗?最终,如果您遵循的是完全正式的方法,那就是正在发生的事情¹。但是代码有很多不同的部分。制动器,摄像机,发动机等都是自主控制的。制动器的正确性将类似于“如果“施加制动器”信号打开则施加制动器。发动机的正确性将是“如果离合器信号关闭,则发动机不驱动车轮”。将它们放在一起需要一个非常高级的视图。规范创建了一个中间层,在其中可以将系统的不同组件连接在一起。

实际上,像汽车自动驾驶仪这样的复杂系统将具有多个级别的规格,并具有不同的改进量。设计中通常使用一种改进方法:从一些高级属性开始,例如“汽车不会撞到墙壁”,然后弄清楚这需要传感器和制动器,并对传感器(制动器)提出一些基本要求。和试验软件,然后再次将这些基本要求完善到组件的设计中(对于传感器,我将需要雷达,DSP,图像处理库等),在正式的开发过程中,从最高级别的属性一直到代码,所有级别的规范都被证明可以满足其上级设置的要求。

不可能确定规范是否正确。例如,如果您弄错了物理原理,即使将刹车规则与形式要求相关的数学方法正确,刹车也可能无效。如果您实际拥有5000kg的负载,则无法证明在500kg的负载下折断是有效的。但是,比起在刹车代码中看到500公斤的重量对汽车的物理参数而言还不够好,更容易发现500公斤是错误的。

¹ 完全正式的方法的对立面是“我想这行得通,但是我不确定”。当您将生命押注在它上面时,这似乎并不那么好。


是否有可能证明我的代码的一个属性,并确保它始终正确,例如,我只想证明数组的索引永远不在数组的范围之内,所以我不在乎其他东西?
Maykel Jakson

5
@MaykelJakson当然!您只是将其用作规格。这可能是一个较弱的规范,但是没有什么可以阻止您使用它并使用正式方法进行证明。
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.