AI-SDLC:用 AI 做关键任务软件工程的一种流程
一个软件项目是一个决策的层级结构,每一个决策都应该由一个其能力与该决策风险相匹配的决策者所拥有。
引言
在大多数软件里,一个缺陷只是一件麻烦事。在关键任务软件里,一个缺陷可能意味着损失金钱、损坏记录、安全事故,或者再也回不来的信任。AI 模型现在已经能快速写出大量代码,这也带来一个困难的问题:我们如何使用这种速度,同时不放弃这些系统所要求的严谨?
常见的答案走向两个极端。一个是让人 review AI 产出的每一行代码。这感觉安全,但它是一种错觉。人类 reviewer 会犯错。他们会累,长时间工作后注意力会下降,而且他们倾向于批准那些看起来合理的代码 — 而这恰恰是 AI 生成代码通常的样子。当人类以人类速度写代码时,逐行 review 就已经不完美了。现在 AI 产出代码的速度超过任何人能读的速度,它就完全不现实了。review 要么变成一个瓶颈,扔掉大部分价值;要么变成一个橡皮图章,给出虚假的信心。
另一个极端把 AI 当作一个自治的工程师:给它一个目标,它交付一个系统。这很快,但在失败代价高昂的地方,它无法被接受。
两个极端都不行,因为它们都在错误的地方寻找安全:在 reviewer 身上,或者在模型身上。在关键任务工程里,安全必须来自流程本身。这意味着决策的清晰归属、显式的契约、独立的验证,以及无论错误是谁犯的都能抓住它的反馈回路。在这样的流程里,人类 review 仍然有价值,但它只是众多可错观察中的一种。它被审慎地用在风险配得上的地方,而不是稀薄地摊在每件事上。
这篇文章描述的就是这个流程。它建立在一个想法和由它推出的少数几条规则之上。
这个想法是:一个软件项目是一个决策的层级结构,每一个决策都应该由一个其能力与该决策风险相匹配的决策者所拥有。
1. 软件是决策的层级结构
每一个软件系统都是决策的总和。最顶层是关于目的的决策:我们解决什么问题、为谁解决、我们不做什么、接受哪些 tradeoff。往下是架构决策:组件如何分工、数据放在哪里、每个边界提供什么保证。再往下是组件内部的设计决策。最底层是执行决策:这个循环、这个变量名、这条错误信息。
人们很容易把这个层级直接映射到一个智能的层级上。人类拥有顶层,前沿模型拥有架构和设计,便宜快速的模型处理执行。这个直觉大部分是对的。意图、价值观和问责属于人类。这不是因为模型无法对它们做推理,而是因为必须有人对结果负责,而问责无法委托给软件。决策之间也确实在所需判断力上有所不同,而成本和延迟在跑成千上万次的内层循环里也确实重要。
但在关键任务的工作里,这个映射的简单版本会以危险的方式失效。它假设低层级的决策是低风险的。
2. 影响半径,而不是层级高度
账单计算里的一个 off-by-one 错误是执行决策。同样,一个造成竞态条件的缺失锁、一个打开注入漏洞的未转义字符串、调度器里的一个时区假设、一次不可逆的数据迁移,都是执行决策。这些没有一个在架构层级里坐得高,而其中任何一个都可能是灾难性的。
所以,决策在层级中的位置是错误的路由依据。正确的度量是影响半径 — 决策做错时的潜在后果。影响半径有几个组成部分:
- 爆炸半径:一个错误会影响系统的多少部分、多少用户。
- 可逆性:错误能否被低成本撤销,还是一扇单向门。
- 可验证性:一个自动化的观察能否可靠地抓住这个错误。
- 歧义性:规格说明是否真的完整,还是执行者必须靠猜。
一个被包含的、可逆的、易于验证的、无歧义的决策,可以交给现有的最便宜最快的模型。一个影响面广的、不可逆的、难以验证的或不清晰的决策,配得上最强的模型、一个人,或者两者 — 无论它看起来多么“低层级”。
于是主导原则变成:一个决策越难验证或越难逆转,它的拥有者就必须越有能力,无论它坐在技术栈的哪一层。
3. 交接契约
一旦决策按影响半径路由,下一个问题就是工作如何在层级之间移动。每当工作被委托 — 无论是人到模型、模型到模型,还是强模型到便宜模型 — 它都跨越一个边界,而上下文就在那里丢失。委托者脑子里的隐性理解被压缩成指令,凡是没有在那次压缩中幸存下来的东西都变成风险。
答案是让每一次交接都成为一份显式契约,并且在每一层都有相同的结构。一份交接声明:
- 意图:这项工作为什么存在,而不仅仅是它是什么。理解了目的的执行者,才能在规格说明没有说到的地方做出合理选择。
- 范围与影响半径:可以触碰什么,以及同样重要的,什么绝不能碰。
- 约束:接口、惯例、不变量和非目标。
- 观察:将观察和验证什么,才能证明工作已完成且正确 — 精确到条目。
- 升级触发器:在什么条件下执行者必须停下并退回。
这份契约是双向运行的。工作交回时,handback 要报告做了什么、每条观察被满足的证据、在规格沉默之处做出的假设、对计划的任何偏离及其原因,以及发现的任何新风险或未决问题。
因为同一份契约适用于每一层,整个层级变成同一个模式的重复。人在这个契约下把工作交给一个前沿模型,那个模型在同一份契约下把工作交给一个更便宜的模型。流程在每一层看起来都一样。
4. 观察才是真正的规格说明
契约里最重要的部分是观察。这里的“测试”要宽泛地读:任何验证行为的观察。它包括类型检查、静态分析、单元测试和属性测试、集成测试、基准、日志、截图、迁移演练、可行之处的形式化证明、人类 review 和生产遥测。
规则很简单:无论交出去的是什么,都必须附带一份将观察和验证什么的规格说明。没有观察的工作无法被有意义地接受,因为不存在一个公认的办法去区分成功和只是看起来像成功的东西。
在关键任务系统里,观察还必须遵守三条进一步的规则。
第一,实现者不得控制观察。任何优化器 — 人或机器 — 都会去满足它被度量的一切,有时走的是最短路径而不是预定的那条。一个承受着通过测试压力的 AI 执行者可能削弱断言、对输入做特殊-case、或者把被测行为 mock 掉。所以测试、验收标准和不变量是受保护的工件。它们由委托者或独立一方定义,对它们的任何修改都触发一次升级。把实现与验证分离是安全关键工程里最古老的原则之一,而当实现者是一个模型时,它更重要而不是更不重要。
第二,观察的严格程度随影响半径缩放。一个小的、可逆的改动可能只需要测试通过。一个影响钱、安全、安防或持久数据的改动需要多个独立的观察:测试、演练、第二个模型的对抗性 review,以及人的签字。纵深防御适用于验证,正如它适用于安全。
第三,验证不在 merge 处结束。有些行为只能在生产中被观察:在真实负载、真实数据和真实用户之下。对于高半径的改动,契约应该包含改动发布后如何被观察 — 通过 canary、指标、告警和分阶段上线 — 以及这些观察失败时的回滚计划。
5. 质疑与升级
契约的好坏取决于它的规格说明,而规格说明永远是不完整的。所以第二条关键规则是:交接的接收者必须质疑任何歧义。
这把委托从单向命令变成一场对话。在执行开始前,接收者检查契约中的缺口和冲突:未定义的边界情况、相互矛盾的约束、并不能真正验证意图的观察。这种 pre-flight 质疑能抓住事先可见的歧义。
但并非所有歧义都是事先可见的。有些歧义只在执行中途出现:一个库原来不支持假设的特性,或一个数据模型无法表达一个真实世界的情况。这就是为什么契约包含升级触发器,以及为什么这些触发器不应该只依赖执行者的自我觉察。知道什么时候超出了自己的深度,本身就是一种复杂的能力,而较弱的模型往往最不擅长。它们倾向于自信地绕过一个问题上,而不是报告计划已经坏了。所以升级还应该从外部强制:反复的验证失败、超出声明范围的修改、对受保护区域(如 authentication、密码学或 schema 迁移)的改动,或者 diff 远超预期地增长。这些中的每一个都应该自动把工作退回更高一层。
质疑还需要校准,因为过多质疑本身就是一种失败。一个就琐碎问题提出十五个疑问的执行者,耗尽了系统里最稀缺的资源 — 人类的注意力。校准同样来自影响半径:
- 高半径歧义:停下来问。
- 低半径歧义:做一个合理的假设,在 handback 里显式记录它,然后继续。
“假设并记录”的路径让工作保持前进,同时确保没有任何假设保持隐藏。每一个猜测都变得可见、可 review、可纠正。
6. 反馈向上流动
传统的层级把执行当作被动的:决策向下流动,结果向上流动。真实的软件不是这样运作的。实现是许多设计缺陷被发现的地方,因为它是抽象第一次与现实相遇的地方。
所以理想的流程把向上反馈当作一等公民的通道。当执行表明一个更高层的决策是错的,正确的回应不是在技术栈底部做一个聪明的 workaround,而是一次升级 — 让那个有缺陷决策的拥有者重新审视它。结构化的 handback 承载着这些信息:偏离、假设和新发现的风险,恰恰是规划层需要的信号。
一个在底部发现的问题能够推翻在顶部制定的计划的系统,不是混乱的标志。它正是让系统自我纠正的东西。
7. 外部记忆:让每一次交接更容易
每一次交接都在上下文上付出代价。流程的最后部分随时间降低这个代价:工作产生的知识被保存在外部记忆里,让每一次后续的交接都从一个更丰富的共享理解出发。
记忆应该映射它所服务的决策层级:
| 层 | 内容 | 谁写 |
|---|---|---|
| 意图与原则 | 目标、非目标、价值观、tradeoff 偏好 | 人类 |
| 决策记录 | 决定了什么、为什么、拒绝了哪些备选方案 | 人类和强模型 |
| 惯例 | 接口、模式、风格、领域词汇 | 强模型写,人类批准 |
| 陷阱与经验 | 什么坏了、为什么、升级如何被解决 | 任何层提议;人工策展 |
| 任务日志 | 交接、handback、假设、证据 | 自动 |
其中有两部分特别有价值:决策背后的推理,以及被拒绝的备选方案。没有它们,未来的 agent 和未来的工程师会重新打开已经解决的问题,或者悄悄撤销深思熟虑的选择。
外部记忆也闭合了反馈回路。每一次升级都应该以一次记忆更新结束。如果同一个歧义不断被升级,那是记忆或契约模板缺了东西,修一次就能防止同一个问题再次出现。
不过,记忆需要治理,因为它以可预测的方式退化。当代码继续前进而记录没有跟上时,它会过时。当一个模型记录了一个错误结论、而每一次后续交接都继承它时,它会被污染。它会膨胀成噪音,把有用的上下文挤出去。解决办法是把流程应用在流程自己身上。写入记忆的权利遵循影响半径:执行者可以提议条目,但重要的条目需要更高一层或人类的批准。条目与代码和日期链接,以便检测过时;记忆被定期策展和摘要,而不只是追加。
8. 人类属于哪里
在这个流程里,人类不是每一行代码的 reviewer,也不只是初始目标的来源。他们握着三个模型无法接管的角色。
他们拥有意图:系统为了什么,哪些 tradeoff 可接受。他们拥有验收:证据是否足以支撑发布的最终判断。他们拥有问责:当某件事失败时,由一个具体的人来回答。
在影响半径要求的地方,人类也直接进入最低层级:安全关键的代码、公开接口、schema 变更,以及任何不可逆的东西。与此同时,人类应该对能力强的模型保持开放 — 它们可能提出比自己更好的设计。人类的工作不是做每一个决策,而是设定目标和约束,然后基于证据接受或拒绝。
9. 度量真正重要的东西
这样的流程如果度量错误的东西,也会欺骗自己。token 成本是最诱人的错误指标。一个需要五次尝试、引入细微缺陷、让 review 时间翻倍的便宜模型并不便宜。正确的度量是端到端的:每个被接受改动的成本、缺陷逃逸率、人类 review 时间,以及检测并从故障中恢复的时间。人类注意力是系统里最昂贵的资源。任何节省算力却增加 review 负载的路由决策都是虚假的节约。
这些度量也应该指导流程自身的演变。模型能力变化很快,今天的前沿能力就是明天的商品。把工作分配到各层的那一层路由应该保持薄、容易重新配置,并且随着指标的变化被重新审视,而不是被冻结成一个围绕今年能力缺口搭建的精巧结构。
10. 这个流程不承诺什么
对限制的诚实是关键任务工程的一部分。这个流程大大减少了上下文丢失,但没有消除它。未知的未知 — 双方都没有意识到的缺口 — 仍然会穿过最仔细的契约。观察可能不完整,记忆可能是错的。这就是为什么这个流程依赖独立验证、分阶段上线、生产可观测性和回滚计划。它假设自己有时会失败,而它被构建为尽早检测失败、快速恢复。
结语
用 AI 做关键任务软件工程的理想流程,不依赖于一个更聪明的模型或一个更巧妙的 prompt。它是一个问责体系,由少数几条规则构建,并在每一层以同样的方式应用:
- 按影响半径路由每一个决策:它的爆炸半径、可逆性、可验证性和歧义性,而不是它在技术栈中的位置。
- 把工作作为契约交接 — 意图、范围、约束、观察和升级触发器 — 并带着证据和记录在案的假设交回。
- 把观察当作真正的规格说明,让它们独立于实现者,并随风险缩放。
- 按影响的比例质疑歧义:赌注高就问,赌注低就假设并记录。
- 让反馈向上流动,让执行中发现的问题能推翻有缺陷的计划。
- 把知识保存在有治理的外部记忆里,让每一次交接都比上一次更容易。
- 让人类对意图、验收和不可撤销的决策负责。
AI 改变的是软件工程能做多少、做得多快。它没有改变关键任务工程一直要求的东西:清晰的所有权、独立的验证、对不确定性的诚实沟通,以及从每一个意外中学习的纪律。模型是新的,但原则是旧的,理想的流程就是那个以机器速度一致地应用这些原则的流程。