AI 时代的软件设计原则
随着大语言模型和编程代理的发展,行业中出现了一种观点:只要写出需求规格说明(Spec),让模型生成代码即可;若出现问题只需修改规格并重新生成,无需关注具体代码实现,因为“代码已经变得极其廉价”。
这种“规格即代码”(Spec-to-Code)的假设在复杂工程中迅速遇到瓶颈。当开发者放弃对代码结构的控制时,演讲借用《程序员修炼之道》所描述的软件熵(Software Entropy)概念指出,混乱会加速累积:局部补丁堆叠、逻辑重复扩散,最终导致代码库迅速劣化。糟糕的代码库不仅不会因为 AI 而变好,反而会使编程代理在盘根错节的依赖中迷失。
AI 编程并没有让经典软件工程过时,反而放大了良好架构的价值。Matt Pocock 在 AI Engineer Europe 演讲中指出了 AI 辅助编程中的 5 种典型失效模式,并结合经典软件设计理论给出了对应的工程实践。
意图偏离:以对抗式提问建立“设计概念”
失效表现
开发者脑海中构想了功能雏形,但给出的提示词或粗粒度规格被模型理解为完全不同的实现方向;或者模型过早制定计划并生成大量非预期代码。
演讲理论依据
演讲借用 Frederick Brooks 在《设计之道》(The Design of Design)中的观点,指出多人或人机协作设计时,核心资产不是写在纸面或 Markdown 里的静态文字,而是流转于协作各方之间的设计概念(Design Concept)——即关于目标系统内在机理与边界的无形共识。
工程实践
在生成代码之前,可以使用 mattpocock/skills 中的 grill-me 或 grill-with-docs 对齐设计:
- 两者共用的
grilling流程把方案展开成决策树,分轮提出当前没有前置阻塞的问题,并为每个问题给出建议答案; grill-with-docs还会结合领域建模,将已经澄清的术语和难以逆转的决策写入GLOSSARY.md与 ADR;- 达成共识后,可另行调用
to-spec或to-tickets,把现有对话整理成规格说明或纵向切片工单。
术语错位:以领域通用语言约束模型上下文
失效表现
模型输出长篇大论,用大量通用修饰词解释行为,但与人类领域专家的概念不在同一频道,导致生成的模型、函数命名与真实业务脱节。
演讲理论依据
演讲引用 Eric Evans 在《领域驱动设计》(Domain-Driven Design)中阐述的通用语言(Ubiquitous Language)原则:业务专家、开发团队与代码实现必须共享同一套严密定义的领域词汇表,消除沟通歧义。
工程实践
- 自动扫描或手动提炼代码库与业务核心概念,建立领域术语 Markdown 表格(如
CONTEXT.md,包含实体名、操作动词、状态流转与明确定义); - 在与代理对话、制定计划和执行重构时始终注入该词汇表;
- 统一术语后,不仅能缩减模型思考轨迹的冗余字数,还能让实现严格贴合领域模型。
行车超速:用 TDD 与类型系统建立最高限速
失效表现
模型一次性输出大批代码,表面结构完整,但实际运行报错,或因缺乏即时验证而越跑越偏。
演讲理论依据
演讲借用 Hunt 与 Thomas 在《程序员修炼之道》(The Pragmatic Programmer)中的比喻:不要超出行车灯范围(Don't outrun your headlights)。在夜间行车时,车灯照亮的距离决定了最高安全车速;在软件开发中,反馈的速度就是前进的最高限速。
工程实践
- 强类型与运行时环境:使用 TypeScript 等强静态类型系统,并为代理提供浏览器/终端等即时执行环境;
- 测试驱动开发(TDD):
- 先编写测试用例,明确期望输入与输出;
- 运行测试,确认当前红灯(失败);
- 让代理编写最简实现,使测试绿灯(通过);
- 重构并优化内部设计。
- TDD 强制大语言模型以小步(Step-by-step)推进,有助于约束一次性失控的风险。
浅层迷失:构建深度模块以隐藏复杂实现
失效表现
代码库充斥着大量微小、细碎的函数和浅层文件,模块之间依赖错综复杂。代理在探索代码时容易迅速耗尽上下文窗口,无法理清调用链路。
演讲理论依据
演讲引入 John Ousterhout 在《软件设计哲学》(A Philosophy of Software Design)中对模块形态的划分:
- 浅层模块(Shallow Modules):接口相对复杂,但内部封装的功能单薄,带来沉重的认知与导航负担;
- 深度模块(Deep Modules):对外暴露极简且稳定的接口,内部封装大量复杂逻辑与状态细节。
工程实践
- 使用
improve-codebase-architecture扫描可深化的模块、应用 deletion test,并将零散的辅助函数与中间状态重构成深度模块; - 精心设计并严格收敛模块外部接口契约;
- 在接口处建立清晰的可测试边界,让内部实现细节对外部隐藏,使代理在局部修改时无需通晓全局。
认知过载:灰盒委托与系统设计投资
失效表现
随着代理交付代码的速度大幅提升,人类开发者若试图逐行审查每一处语法与局部逻辑,会迅速陷入严重的认知疲劳与审查瓶颈。
演讲理论依据
演讲结合了 Kent Beck 强调的“每日投资于系统设计”:软件工程的核心价值在于对系统边界、数据流向与组件职责的战略权衡,而非机械的代码敲击。
工程实践
- 灰盒委托(Gray-Box Delegation):
- 在非核心边界模块中,将深度模块视为“灰盒”;
- 开发者只把控外部接口契约与集成测试边界,将模块内部细节实现委托给代理;
- 在金融计算、安全鉴权、基础设施等高风险核心路径上,仍保持严格的白盒审查。
- 角色分工:人类扮演战略架构师(Strategic Architect),负责需求解构、接口设计与验证标准;AI 扮演战术程序员(Tactical Programmer),负责契约内部的代码填充与测试执行。
核心原则与实践对照
实践边界与适用场景
- 灰盒委托的前提是完备的测试边界:如果缺乏严格的接口自动化测试,灰盒委托会直接退化为放任代码质量劣化。
- 高安全领域不可完全灰盒化:涉及权限验证、支付结算、加密通信与数据迁移的核心代码,必须保持人类对实现细节的逐行白盒审查。
- 架构重构先于批量生成:在让代理处理复杂任务前,先通过重构将浅层模块收敛为深度模块,有助于改善代码的可导航性。
来源与工具定义
上文五组“失效模式—工程原则”来自 Pocock 的演讲;工具行为则以当前仓库中的定义为准: