计算机科学里的“海森堡原理”:为什么我们永远无法完全预测代码的“副作用”?

21 阅读4分钟

在理论计算机科学中,有一个令人绝望的定理叫 Rice’s Theorem(莱斯定理)

它告诉我们一件看似反直觉、却极其严肃的事实:

任何关于程序“行为”的非平凡性质,都无法通过静态分析被完美判定。

换句话说——
如果你想在程序运行之前,通过检查代码、分析结构、甚至借助再强的工具,来百分之百确定它“会不会做坏事”
在数学意义上,这是不可判定的


1. 为什么“预测程序行为”在理论上行不通

Rice’s Theorem 并不是一个工程结论,而是一个编程语言理论层面的定理

它不关心你的程序是 Python、C++ 还是汇编;
也不关心你是否使用了最先进的分析工具。

它只断言一件事:

只要一个程序的行为不是“恒真”或“恒假”,
那么判断它的行为,本身就是一个不可判定问题。

这意味着:

  • “这个程序会不会删除文件?”
  • “这个程序会不会发送网络请求?”
  • “这个程序会不会进入死循环?”

这些问题,在一般意义上,都不存在一个完美的静态解法。

这并不是工程能力不足,
而是计算机科学对我们施加的硬性上限


2. 副作用(Side Effects):不可预测性的根源

在编程语言理论中,这种不可预测性通常被归结为一个概念:

Side Effects(副作用)

一个程序如果只是把输入映射为输出(像数学函数一样),
那么它的行为是可推理、可组合、可预测的。

但现实世界中的程序几乎从不如此“纯净”。

以下行为,全部属于副作用:

  • 写入文件
  • 修改数据库
  • 发送网络请求
  • 修改全局变量
  • 创建 / 删除进程
  • 打印日志(是的,打印日志也是副作用)

一旦副作用出现,程序就不再是一个“表达式”,而是一个“事件序列”。


一个具象化的反例

import os, random

if random.randint(0, 1) == 1:
    os.remove("data.txt")

通过静态分析,我们最多只能得出一个结论:
“这段代码可能会删除文件。”

但我们无法在运行前判断:

  • 这一次执行会不会真的删除?
  • 删除是否依赖随机数或外部状态?

更极端的情况是:

eval(input())

此时,程序的真实行为完全取决于运行时输入。
无论静态分析工具多么复杂,都无法覆盖所有可能性。


3. 一个恰当的类比:计算机科学里的“海森堡原理”

在量子力学中,海森堡不确定性原理揭示了一件事:

观测行为本身,会改变被观测对象的状态。

在有副作用的程序中,也存在一个高度相似的现象:

执行行为本身,会改变系统状态。

  • 在代码被执行之前,它只是文本
  • 一旦执行,它就开始改变世界状态
  • 状态一旦改变,就无法回到“未执行”的原点

4. 函数式编程的态度:承认不确定性

函数式编程语言(如 Haskell)并不幻想消灭副作用,而是选择:

把副作用显式化、隔离化、类型化。

例如:

  • Referential Transparency
  • IO Monad
  • 类型系统强制标注副作用

这并没有消灭副作用,但避免了“隐形副作用”。


5. 工程现实:动态语言中的失控半径

在 Python 等动态语言中:

  • 程序可以同时访问文件系统和网络
  • 几乎没有默认的权限提示
  • 执行能力被完全交给了代码本身

这使得执行的爆炸半径被无意中放大。


6. 边界控制:工程上的现实答案

既然预测存在理论上限,工程系统真正能做的,往往不是更聪明的预测,而是:

限制副作用一旦发生时的影响范围。

这正是:

  • Least Privilege
  • Capability-based Security
  • Bulkhead Pattern

等工程思想的核心。

image.png

7. 一个工程实验:FailCore

基于上述思考,我进行了一个工程实验,尝试在运行时而非运行前,对即将发生的副作用进行判断。

这个实验性项目被我称为 FailCore

它并不是为了解决 Rice’s Theorem,而是承认它的存在,并在工程上绕开它的限制,作为一次围绕“执行边界”问题的工程化探索。


写在最后

计算机科学中,有些问题并不是尚未解决,而是被证明无法彻底解决。

副作用的不可预测性,就是其中之一。

真正成熟的系统,往往不是更聪明,而是更克制。