跳到主要内容

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-megrill-with-docs 对齐设计:

  • 两者共用的 grilling 流程把方案展开成决策树,分轮提出当前没有前置阻塞的问题,并为每个问题给出建议答案;
  • grill-with-docs 还会结合领域建模,将已经澄清的术语和难以逆转的决策写入 CONTEXT.md 与 ADR;
  • 达成共识后,可另行调用 to-specto-tickets,把现有对话整理成规格说明或纵向切片工单。

术语错位:以领域通用语言约束模型上下文

失效表现

模型输出长篇大论,用大量通用修饰词解释行为,但与人类领域专家的概念不在同一频道,导致生成的模型、函数命名与真实业务脱节。

演讲理论依据

演讲引用 Eric Evans 在《领域驱动设计》(Domain-Driven Design)中阐述的通用语言(Ubiquitous Language)原则:业务专家、开发团队与代码实现必须共享同一套严密定义的领域词汇表,消除沟通歧义。

工程实践

  • 自动扫描或手动提炼代码库与业务核心概念,建立领域术语 Markdown 表格(如 CONTEXT.md,包含实体名、操作动词、状态流转与明确定义);
  • 在与代理对话、制定计划和执行重构时始终注入该词汇表;
  • 统一术语后,不仅能缩减模型思考轨迹的冗余字数,还能让实现严格贴合领域模型。

行车超速:用 TDD 与类型系统建立最高限速

失效表现

模型一次性输出大批代码,表面结构完整,但实际运行报错,或因缺乏即时验证而越跑越偏。

演讲理论依据

演讲借用 Hunt 与 Thomas 在《程序员修炼之道》(The Pragmatic Programmer)中的比喻:不要超出行车灯范围(Don't outrun your headlights)。在夜间行车时,车灯照亮的距离决定了最高安全车速;在软件开发中,反馈的速度就是前进的最高限速

工程实践

  • 强类型与运行时环境:使用 TypeScript 等强静态类型系统,并为代理提供浏览器/终端等即时执行环境;
  • 测试驱动开发(TDD)
    1. 先编写测试用例,明确期望输入与输出;
    2. 运行测试,确认当前红灯(失败);
    3. 让代理编写最简实现,使测试绿灯(通过);
    4. 重构并优化内部设计。
  • 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),负责契约内部的代码填充与测试执行。

核心原则与实践对照

维度经典工程原则演讲所引理论家AI 编程失效模式代理工程实践
意图设计概念(Design Concept)Frederick Brooks (The Design of Design)意图偏离、生成非预期代码对抗式提问(grill-me)、充分对话后再生成资产
术语通用语言(Ubiquitous Language)Eric Evans (Domain-Driven Design)术语错位、长篇大论脱节领域词汇表 Markdown(CONTEXT.md)、统一命名契约
步进不要超出行车灯范围Hunt & Thomas (The Pragmatic Programmer)一次性失控、生成无法运行的代码TDD 步进反馈、强静态类型系统
架构深度模块(Deep Modules)John Ousterhout (A Philosophy of Software Design)细碎依赖导致上下文迷失极简接口、高内聚封装、接口层测试边界
协作系统设计投资 / 灰盒委托Kent Beck人类逐行审查陷入认知过载人类把控接口与测试,AI 承接内部实现

实践边界与适用场景

  1. 灰盒委托的前提是完备的测试边界:如果缺乏严格的接口自动化测试,灰盒委托会直接退化为放任代码质量劣化。
  2. 高安全领域不可完全灰盒化:涉及权限验证、支付结算、加密通信与数据迁移的核心代码,必须保持人类对实现细节的逐行白盒审查。
  3. 架构重构先于批量生成:在让代理处理复杂任务前,先通过重构将浅层模块收敛为深度模块,有助于改善代码的可导航性。

来源与工具定义

上文五组“失效模式—工程原则”来自 Pocock 的演讲;工具行为则以当前仓库中的定义为准: