如何用AI写出好的代码
在 AI 时代,烂代码的代价比以往任何时候都更高
- 如果代码库难以修改,你就无法充分利用 AI 带来的所有优势
- AI 在良好的代码库上表现非常出色
- 因此,
好的代码库比以往任何时候都更重要,软件基础原则也比以往任何时候都更重要
失败的解决方案
模式一:AI 没有做你想要的事
症状:头脑中有一个好的想法,但 AI 做了完全不同的事情,或者生成了你不想要的东西。
原因:你和 AI 之间存在沟通障碍,缺乏共享的设计概念(Design Concept)。
概念——设计概念(Design Concept)
出自 Frederick Brooks 的《设计的设计》
当多人共同设计某样东西时,会有一个想法在你们之间飘浮——这就是"设计概念"。它是关于你要构建的东西的无形理论,不是可以放在 markdown 文件中的资产。
解决方案:Grilling Me Skill
Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions, one by one.原理:
- 让 AI 向你
问 40-60 个问题(甚至 100 个),直到达到共享理解 - 将对话转化为产品
需求文档或 issue - AFK agent(离线代理)随后接手
建议:在开始使用工具的默认 Plan 模式之前,先达到共享的设计概念会更好。
模式二:AI 太冗长
症状:与 AI 交流时感觉不在同一个频道上,它用太多话解释它在做什么。
原因:开发者与 AI 之间存在语言鸿沟,就像开发者与领域专家之间的鸿沟一样。
概念——统一语言(Ubiquitous Language)
出自领域驱动设计(Domain-Driven Design, DDD)
开发者之间的对话、代码中的表达式、与领域专家的对话,都应源自同一个领域模型。
统一语言本质上是一个 markdown 文件,列出了你和 AI共同拥有的术语列表。
解决方案:Ubiquitous Language Skill
工作流程:
- 扫描代码库,识别术语
- 创建 markdown 文件,包含术语的表格
- 将这个文件传递给 AI
- 在与
AI 协作时始终保持打开参考
效果:
- 改善规划质量
- 让
AI 以更简洁的方式思考 - 实现更符合计划精神的代码
模式三:AI 构建了正确的东西但不工作
症状:AI 理解了需求并构建了正确的东西,但代码不工作。
解决:建立反馈循环
- 使用静态类型(TypeScript 强烈推荐)
- 让 LLM 访问浏览器以便探索
自动化测试
模式四:LLM 过度超前(Outrunning Your Headlights)
症状:AI 一次做太多事情,产生大量代码后才发现类型错误或测试失败。
概念——超越你的车灯(Outrunning Your Headlights)
出自《实用程序员》
反馈的速度就是你的速度极限。你应该边做边测试,采取
小而谨慎的步骤。AI 默认情况下非常不擅长这一点。
解决方案:测试驱动开发(TDD)
TDD 强制 LLM 采取小步骤:
- 先创建测试
- 让测试通过
- 重构代码以改善设计
为什么测试很难
编写测试需要做出许多相互依赖的决策:
- 要测试多大的单元?
- 要 mock 什么?
- 首先要测试什么行为?
关键:好的代码库 = 易于测试的代码库
模式五:代码能工作但大脑跟不上
症状:反馈循环运行良好,代码产出比以往任何时候都多,但大脑疲惫不堪。
原因:代码库让 AI 和你都需要在脑中同时处理太多信息。
解决方案:设计接口,委托实现
将深模块视为**灰盒**:
- 设计好接口
- 不需要深入审查实现细节
- 用测试从外部验证
注意:对于金融等关键模块不能这样做,但对于应用中许多其他模块可以。
效果:节省脑力,专注于战略层面的设计。
软件基础原则
1. 软件熵(Software Entropy)
出自《实用程序员》
熵是事物趋向灾难和崩溃的倾向。每次你修改代码库时,如果只考虑那个变更而不考虑整个
系统的设计,你的代码库会变得越来越糟糕。
教训:spec-to-code 的做法只会让代码越来越差。
2. 深度模块化(Deep Modules)
出自 John Ousterhout 的《软件设计的哲学》
| 类型 | 特点 | 示例 |
|---|---|---|
| 深度模块 | 大量功能隐藏在简单接口后面 | ✅隐藏复杂性,按需查看内部 |
| 浅层模块 | 功能不多,接口却很复杂 | ❌ AI 需要在大量小模块间导航,难以理解 |
架构改造技能:Improved Codebase Architecture
将浅层模块代码库改造为深层模块:
- 探索代码库
- 寻找相关的代码
- 将它们包装在一个深模块中
- 在接口处进行测试
3. 统一语言(Ubiquitous Language)
核心原则:
- 代码、对话、文档使用相同的术语
- 确保术语与实际含义一致
- 术语需要成为 ubiquitous language 的一部分
4. 投资系统设计
来自 Kent Beck
每天都要投资于系统的设计。
spec-to-code 的问题:不是在投资系统设计,而是在撤资。
架构视图对比
浅层模块架构
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ 模块 │ │ 模块 │ │ 模块 │ │ 模块 │ │ 模块 │ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ (大量小 blob,AI 需要逐一导航)深层模块架构
┌─────────────────────────────┐ │ 接口层 │ │ ┌─────────┐ ┌─────────┐ │ │ │ 接口 A │ │ 接口 B │ │ │ └────┬────┘ └────┬────┘ │ └───────┼───────────┼───────┘ ▼ ▼ ┌─────────────┐ ┌─────────────┐ │ 深模块实现 │ │ 深模块实现 │ │ (可委托) │ │ (可委托) │ └─────────────┘ └─────────────┘AI 协作中的角色分工
| 角色 | 职责 |
|---|---|
| AI | 地面战术级程序员,执行具体的代码变更 |
| 人类 | 战略级思考者,负责设计、架构、决策 |
AI 是一个出色的战术级程序员,但需要人类在上面进行战略层面的思考。这正是软件基础技能的价值所在。
参考文献: github.com/mattpocock/skills
相关书籍
| 书名 | 作者 | 核心概念 |
|---|---|---|
| 《软件设计的哲学》 | John Ousterhout | 深度模块、代码复杂性 |
| 《实用程序员》 | David Thomas, Andrew Hunt | 软件熵、TDD |
| 《设计的设计》 | Frederick Brooks | 设计概念 |
| 《领域驱动设计》 | Eric Evans | 统一语言(Ubiquitous Language) |