在理论计算机科学中,有一个令人绝望的定理叫 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
等工程思想的核心。
7. 一个工程实验:FailCore
基于上述思考,我进行了一个工程实验,尝试在运行时而非运行前,对即将发生的副作用进行判断。
这个实验性项目被我称为 FailCore。
它并不是为了解决 Rice’s Theorem,而是承认它的存在,并在工程上绕开它的限制,作为一次围绕“执行边界”问题的工程化探索。
写在最后
计算机科学中,有些问题并不是尚未解决,而是被证明无法彻底解决。
副作用的不可预测性,就是其中之一。
真正成熟的系统,往往不是更聪明,而是更克制。