我读过很多文章,其中指出代码不可能没有错误,并且他们在谈论这些定理:
实际上,赖斯定理看起来像是中止问题的一个暗示,而中止问题与哥德尔的不完备性定理密切相关。
这是否意味着每个程序都会有至少一个意外行为?还是意味着无法编写代码进行验证?递归检查呢?假设我有两个程序。他们两个都有错误,但是没有共享相同的错误。如果同时运行它们会发生什么?
当然,大多数讨论都涉及图灵机。线性有界自动化(真实计算机)如何?
我读过很多文章,其中指出代码不可能没有错误,并且他们在谈论这些定理:
实际上,赖斯定理看起来像是中止问题的一个暗示,而中止问题与哥德尔的不完备性定理密切相关。
这是否意味着每个程序都会有至少一个意外行为?还是意味着无法编写代码进行验证?递归检查呢?假设我有两个程序。他们两个都有错误,但是没有共享相同的错误。如果同时运行它们会发生什么?
当然,大多数讨论都涉及图灵机。线性有界自动化(真实计算机)如何?
Answers:
并不是说程序不能没有错误,还不止这些。如果您要证明的程序是不平凡的,那就很难证明它们是。
请注意,这不是因为缺乏尝试。类型系统应该提供某种保证;Haskell在某种程度上具有高度复杂的类型系统。但是您永远无法消除所有不确定性。
考虑以下功能:
int add(int a, int b) { return a + b; }
此功能可能出什么问题?我已经知道你在想什么 假设我们已经涵盖了所有基础,例如检查溢出等。如果宇宙射线撞击处理器,导致处理器执行,会发生什么情况?
LaunchMissiles();
代替?
好吧,也许这有点做作。但是,即使是像add
上面的函数这样的简单函数也必须在处理器不断更改上下文,在多个线程和内核之间切换的环境中运行。在这样的环境中,任何事情都可能发生。如果您对此表示怀疑,请考虑RAM是可靠的,不是因为它没有错误,而是因为它具有一个内置系统来纠正不可避免地发生的位错误。
我知道你在想什么 “但是我说的是软件,而不是硬件。”
有许多技术可以提高您对该软件以预期方式工作的置信度。函数式编程就是其中之一。函数式编程使您可以更好地考虑并发性。但是函数式编程不是证明,而是单元测试。
为什么?因为您拥有这些称为“ 边缘案例”的东西。
而且,一旦您仅获得了简单性之外return a + b
,证明程序的正确性就变得非常困难。
int add(int a, int b) { return a - b; }
…
首先,让我们确定您希望在其中进行讨论的上下文。Stack Exchange的程序员问答(Q&A)建议您最有可能对工具/语言在现实世界中的存在感兴趣,而不是对理论结果和计算机科学定理感兴趣。
我读过很多文章,指出代码不能没有错误
我希望不会,因为这样的说法是不正确的。据我所知和经验,虽然大多数大型应用程序并非一无是处,这是公认的。
更为普遍的接受观点是,不存在(即存在,没有可能性)能够完美确定以图灵完备的编程语言编写的程序是否完全没有错误的工具。
非证明是对停顿问题的直观扩展,结合了日常经验的观察数据。
有确实存在的软件,可以做“的证明正确性是验证程序符合相应的” 正式的规范程序。
这是否意味着每个程序都会有至少一个意外行为?
否。尽管已发现大多数应用程序至少有一个错误或意外行为。
还是意味着无法编写代码进行验证?
不可以,您可以使用正式的规范和证明助手来验证是否遵循规范,但是经验表明,整个系统中仍然存在错误,例如规范之外的因素-源代码转换器和硬件,并且最常见的错误是在规格本身。
有关更多细节,请参阅Coq是这样的工具/语言/系统。
递归检查呢?
我不知道。我不熟悉它,也不确定是可计算性问题还是编译器优化问题。
我想问一下,这是否意味着每个代码都会得到至少一个意外的行为?
否。可以编写正确的程序。你要知道,一个程序是正确的,但它的执行可能失败,例如物理环境(如罗伯特·哈维在他的回答中写道,用户在这里),但是这是一个不同的事情:该程序的代码仍然是正确的。更准确地说,该故障不是由程序本身的错误或错误引起的,而是由执行该故障的基础计算机(*)引起的。
(*)根据其规范,我从可靠性字段中借用了错误,错误和失败的定义,分别是静态缺陷,不正确的内部状态和不正确的外部观察到的行为(请参见<该领域的任何论文>) 。
或者,这是否意味着我无法编写代码,它将对其进行检查?
请参考上面陈述中的一般情况,您是正确的。
您可能可以编写检查特定X程序是否正确的程序。例如,如果您将“ hello world”程序定义为依次具有两个指令的程序,即print("hello world")
和exit
,则可以编写一个程序,以检查其输入是否为依次由这两个指令组成的程序,从而报告该程序是否为是否正确的“ hello world”程序。
使用当前的公式,您不能做的是编写一个程序来检查是否有任意程序暂停,这意味着在一般情况下不可能进行正确性测试。
运行同一程序的两个或多个变体是一种众所周知的容错技术,称为N变量(或N版本)编程。这是对软件中存在错误的承认。
通常,这些变体由不同的开发团队使用不同的编译器进行编码,有时在具有不同OS的不同CPU上执行。在将结果输出给用户之前,会对结果进行投票。波音和空客都喜欢这种架构。
仍然存在两个弱链接,从而导致共模错误:
程序具有某些规范,并且可以在某些环境中运行。
(在别人的答案改变宇宙射线例如add
到FireMissiles
可以被认为“环境”的一部分)
假设您可以正式指定程序的预期行为(即其规范)及其环境,则有时您可以正式证明该程序在某种意义上没有错误(因此,其行为或输出在规范化中尊重其规范的形式化)的环境)。
特别是,您可能会使用声音静态源分析仪,例如Frama-C。
(但目前此类分析仪的最新技术不允许对整个程序进行分析,也无法对大型程序(如GCC编译器,Firefox浏览器或Linux内核)进行证明;我相信,这种证明不会在我的一生中发生。 (我出生于1959年)
但是,您已证明在某些(类)环境中具有某些特定规范的程序的正确行为。
实际上,您可以(而且NASA或ESA可能希望)证明某些航天器软件是“无错误的”,并且具有一些精确的和规范化的规范。但这并不意味着您的系统将始终表现为所需的状态。
用简单的话来说,如果您的航天器机器人满足某些ET的要求,而您没有指定它,则无法让机器人表现出您真正想要的行为。
另请阅读J.Pitrat的博客条目。
顺便说一句,暂停问题或哥德尔定理可能也适用于人脑,甚至人类。
Add
来LaunchMissiles
将是一个SEU改变一些数据值,最终将导致错误的调用LaunchMissiles
。SEU是进入太空的计算机的问题。这就是为什么现代航天器通常会飞行多台计算机的原因。这增加了一组新的问题,并发和冗余管理。
这是否意味着每个程序都会有至少一个意外行为?
没有。
暂停问题表明不可能编写一个程序来测试每个程序是否在有限的时间内暂停。这并不意味着不可能编写一个程序将某些程序分类为停止,而另一些程序则分类为非停止。这意味着总是存在一些暂停分析器无法将其分类的程序。
哥德尔的不完全性定理与它们的灰色区域相似。给定一个具有足够复杂性的数学系统,在该系统的上下文中将存在一些无法评估其准确性的陈述。这并不意味着数学家必须放弃证明的思想。证明仍然是数学的基石。
某些程序可以证明是正确的。这并不容易,但是可以做到。这就是形式定理证明(形式方法的一部分)的目标。哥德尔的不完备性定理在这里确实起作用:并非所有程序都可以证明是正确的。这并不意味着完全使用正式方法是没有用的,因为某些程序确实可以被正式证明是正确的。
注意:正式方法排除了宇宙射线撞击处理器并launch_missiles()
代替执行的可能性a+b
。他们在抽象机器的环境中分析程序,而不是像罗伯特·哈维(Robert Harvey)的宇宙射线那样受单事件影响的真实机器来分析程序。
这里有很多很好的答案,但是它们似乎都绕过了关键点,这就是:所有这些定理都具有相似的结构,并说出类似的话,而他们说的不是 “不可能写出正确的程序”(对于某些特定的值‘正确’和‘方案’指出,各有不同的情况下),但它们是什么做的说的是‘这是不可能的,以防止有人写不正确的程序,我们无法证明是不正确的’(等等)。
以停止问题的特定示例为例,区别变得更加明显:显然,有些程序可以停止运行,而其他程序则永不停止。如果我们只想编写一个可证明停止的程序,那么第三类程序的行为无法确定就不是问题,因为我们可以避免编写属于该类的程序。
莱斯定理也是如此。是的,对于程序的任何非平凡属性,我们可以编写一个既不能证明该属性为真也不能为假的程序。我们还可以避免编写这样的程序,因为我们能够确定程序是否可证明。
我的回答将是从现实世界的业务以及每个开发团队面临的挑战的角度出发。我在这个问题和很多答案中看到的实际上是关于控制缺陷的。
代码可以没有错误。为任何编程语言获取任何“ Hello World”代码示例,然后在预期的平台上运行该示例,它将一致地工作并产生所需的结果。关于代码不可能没有错误的任何理论到此为止。
随着逻辑变得越来越复杂,潜在的错误也随之而来。简单的Hello World示例没有逻辑,并且每次都执行相同的静态操作。一旦添加了逻辑驱动的动态行为,就会引入导致错误的复杂性。逻辑本身可能有缺陷,或者输入到逻辑的数据可能会以逻辑无法处理的方式发生变化。
现代应用程序还依赖于运行时库,CLR,中间件,数据库等层,这些层在总体上节省了开发时间的同时,也是这些层中可能存在错误且无法通过开发和UAT测试进入生产并被发现的层。
最后,应用程序消费提供其逻辑的数据的应用程序/系统链都是其逻辑内,逻辑所基于的软件堆栈或逻辑消费的上游系统中的所有潜在错误的源。
开发人员并不能完全控制支持其应用程序逻辑的每一个动作。实际上,我们没有太多控制权。这就是为什么单元测试很重要,而配置和变更管理是重要的过程的原因,我们不能忽略或懒惰/草率。
此外,您的应用程序之间的书面协议,该协议将使用来自您控制之外的源的数据,该协议定义了所传输数据的特定格式和规范,以及系统假设源系统负责确保输出在内部的任何限制或约束。这些界限。
在实际的软件工程应用程序中,您无法通过向业务部门解释理论上为什么应用程序不能没有bug来使它飞速发展。除非发生技术故障会影响企业的盈利能力,防止亏损和/或维持人们的生命力,否则技术与企业之间这种性质的讨论将永远不会发生。“如何发生”的答案不能是“让我解释一下这个理论以便您理解”。
从理论上讲,可能需要大量时间执行计算并获得结果的大规模计算,这是一个无法完成并返回结果的应用程序-这是一个错误。如果计算的性质非常耗时且计算量大,则可以接受该请求并向用户提供反馈,告知他们如何/何时可以检索结果,并启动并行线程以对其进行搅动。如果这需要比在一台服务器上更快地完成并且在业务上足够重要,那么您可以根据需要在多个系统上进行扩展。这就是为什么云非常吸引人,以及能够增加节点进行工作并在完成后将其减少的能力的原因。
如果有可能收到无法完成任何计算能力的请求,则不应将其闲逛到无限地等待业务流程,等待业务认为有限问题的答案。
我不相信代码永远不会100%错误,因为代码从未真正完成。您可以随时改进自己的写作。
编程是科学和数学的领域,在这种情况下,两者都是无止境的。成为开发人员的伟大之处在于我们的工作是无止境的。
有上千种方法可以编写一行代码。想法是编写该行代码的最优化版本,但这可能并非没有错误。无错误是指您的代码是坚不可摧的,并且所有代码都可以在某种程度上或某些方法被破坏。
那么代码可以有效吗?是。
可以无休止地优化代码吗?是。
代码可以没有错误吗?不,您只是还没有找到打破它的方法。
话虽如此,如果您努力改善自己和代码编写实践,那么您的代码将很难破解。
print "Hello, World!"
...您能更清楚一点吗?