当实现无法被完整阅读,软件控制需要哪些证据?本文建立需求、契约、测试、实现分析与运行监控组成的五层保证体系。
假设 AI 生成了一个转账函数,单元测试全部通过。我们能否据此放心上线?
还不能。测试可能只覆盖余额充足的正常路径,没有检查重复请求、并发扣款、货币精度、审计记录或下游超时。即使每一行代码都由人写,问题也一样存在。阅读能够发现某些错误,却无法穷尽输入、状态和环境;测试能够提供行为证据,也无法证明需求本身完整。
上一篇讨论团队理解与系统之间的认知债。本篇转向工程保证:当完整理解不可得时,怎样建立足够但不虚假的信心?
接口契约、不变量、测试先行和形式验证都早于生成式 AI。它们不是黑箱时代突然出现的新方法,也没有取代实现检查。AI 改变的是相对成本:候选实现更容易产生,明确性质和核验证据便显得更稀缺。
因此,更准确的方向不是二选一,而是从单一证据转向分层保证:
说明我们主张系统满足什么,用相互独立的方法支持这项主张,并诚实记录仍未覆盖的条件。
这与安全关键领域中的 assurance case 思路相近。控制不是“测试绿了”的感觉,而是一条可以被质疑的主张—证据链。
转账功能的第一层不是代码,而是业务和风险要求:
这些要求包含功能、禁止行为和失败处理。若团队只给模型一句“实现转账 API”,生成器会用训练数据中的常见模式填补空白。实现可以整洁,却不一定符合这个组织的真实制度。
需求层的证据来自领域专家确认、威胁建模、历史事故、法规和用户场景。它的盲点是自然语言可能歧义,利益相关者也可能遗漏风险。
契约把部分要求变成更精确的边界:
类型系统、数据库约束、状态机和权限策略都可以承担这种工作。它们让一部分错误变得不可表达,或者在边界处立即失败。
但契约不会自动正确。若把“每天限额”写错,机器可以完美验证一个错误规则;若性质没有包含隐私泄漏,形式上的资金守恒也不能证明系统安全。约束减少行为空间,不替我们决定哪一个空间值得允许。
示例测试适合固定已知场景;性质测试从更广输入中寻找反例;状态机或模型测试适合检查操作序列;模糊测试可以探索解析器和协议边界;故障注入则检查依赖失败时的系统反应。
对于转账系统,可以让生成器产生不同金额、账户状态、重复请求和并发顺序,持续检验余额守恒与幂等性。QuickCheck 在 2000 年已经展示了用随机数据检验程序性质的方法,这并不是 AI 时代的新发明。
测试的力量在于找到具体反例,而不是证明“所有可能情况都没问题”。测试数据分布、模拟环境和断言质量都会限制结论。AI 可以帮助提出边界案例,也可能生成与实现共享同一误解的测试,因此高风险性质需要独立来源。
代码审查、静态分析、依赖扫描、动态分析和渗透测试仍然重要。行为测试可能发现“发生了错误”,实现分析更容易解释错误怎样发生,以及是否存在同类模式。
代码覆盖率在这里仍有用途:它能指出测试从未执行的语句或分支。它不能证明需求已经覆盖,也不能衡量断言质量。所谓“行为覆盖”如果不定义行为模型、输入分区和性质集合,就只是一个好听的新标签。
更合理的关系是:
这些维度互补,没有一个可以单独取代其余部分。
形式验证可以证明实现相对于形式化规格满足某些性质。seL4 微内核展示了机器检查的功能正确性证明能够达到很深层级,同时也明确列出编译器、硬件和规格等可信假设。
这正说明形式证明的边界:它证明的是模型中写出的性质,不是现实世界的一切正确性。完整验证成本高,也不必施加在每段低风险业务代码上。
更现实的做法是风险分配:
AI 可能帮助生成规格或证明,但规格与证明脚本仍需独立检查。由同一个模型同时提出实现、测试和“证明”,容易产生相关性失败。
前五层都无法完整复制生产环境。真实流量、数据分布、依赖故障和运维操作会暴露新的条件,因此保证必须延伸到运行期:
运行监控不是承认前面的工程失败,而是承认开放系统总会遇到模型外事实。
| 层级 | 转账案例中的主张 | 主要证据 | 不能单独证明 |
|---|---|---|---|
| 需求 | 这是正确的业务与风险目标 | 领域评审、威胁模型 | 要求无遗漏 |
| 契约 | 关键状态必须守恒 | 类型、约束、不变量 | 契约符合现实意图 |
| 测试 | 代表性场景满足性质 | 示例、性质、故障注入 | 输入空间已经穷尽 |
| 实现 | 没有已知结构性缺陷 | 审查、静态与动态分析 | 所有行为正确 |
| 形式验证 | 指定模型满足指定性质 | 机器检查证明 | 模型外环境安全 |
| 运行验证 | 真实系统保持在边界内 | 监控、对账、灰度 | 未来不会出现新风险 |
这张表的价值不在于让流程变长,而在于防止证据越权。测试通过只能支持它实际检查的主张,代码可读也只能提高发现某些错误的机会。
黑箱系统最危险的不是我们没有读过全部实现,而是我们无法说清自己凭什么相信它。
分层保证保留了理解的价值,也承认理解的边界。工程师可以阅读关键实现,同时用契约限制状态、用测试搜索反例、用形式方法证明高风险性质,并在运行期准备发现和恢复。
控制不可读之物,不是把内部过程忘掉,而是让每项重要主张拥有与其风险相称的证据。