用 AI 写架构决策记录(ADR)——工程文化的意外收获
用 AI 写架构决策记录(ADR)——工程文化的意外收获
适读人群:Tech Lead、有决策记录需求的工程师 | 阅读时长:约13分钟 | 核心价值:AI辅助写ADR的真实实践,含before/after案例,以及这件事对团队的影响
大概一年半前,我在一家创业公司做 Tech Lead。
那时候团队有个问题:我们做了很多技术决策,但没有记录。新人来了,不知道为什么代码是这个样子;半年后重新讨论同样的问题,又重复了之前的辩论;有人离职,当初的考量随着人一起消失了。
我知道应该写 ADR(Architecture Decision Record),但每次到了真要写的时候,面对空白文档就发憷——不是不知道决策是什么,而是整理成文字太麻烦,写完感觉又长又干,也不确定有没有遗漏什么重要的考量。
后来我试着用 AI 辅助写 ADR,这件事超出了我的预期。不只是省时间,而是改变了我写 ADR 的方式,进而改变了我们团队的决策质量。
ADR 是什么,为什么重要
ADR 是一种记录重要架构决策的文档,格式通常包含:
- 背景:当时面临的问题或需求是什么
- 决策:我们决定怎么做
- 备选方案:我们考虑过哪些其他选择
- 后果:这个决策带来了什么,包括好的和不好的
好的 ADR 让未来的人(包括6个月后的你自己)能理解为什么代码/系统是这个样子,而不是只看到结果。
问题在哪里:ADR 写起来难在哪
我写 ADR 遇到的障碍主要有三个:
障碍一:整理思路很累
决策是在讨论中产生的,思路是分散的。把散乱的讨论整理成条理清晰的文档,需要大量的认知能力,经常觉得"懒得整理,就这么放着吧"。
障碍二:备选方案容易不完整
人在做决策时很容易有路径依赖,只记录自己考虑过的方案,忘了或者不知道还有其他可能性。
障碍三:后果分析不够深入
"这个决策的坏处是什么"——这是最难写的部分。人倾向于为自己的决策辩护,对缺点描述不够诚实,ADR 就失去了真正的价值。
AI 在这三个障碍上都能帮上忙,但方式不同。
实际流程
我现在的 ADR 写作流程是:
第一步:脑倾倒(brain dump)
不管格式,把关于这个决策的所有信息丢给 AI:背景是什么,遇到了什么问题,有哪些选项,最后选了什么,大概为什么选这个。语无伦次没关系。
第二步:让 AI 整理成草稿
AI 把 brain dump 整理成有结构的 ADR 草稿。
第三步:AI 提出我可能忽视的备选方案和问题
这一步很关键。让 AI 扮演一个"挑战者":对我的决策提出质疑,列出我可能没考虑到的备选方案,指出决策逻辑里可能的漏洞。
第四步:我来填充AI不知道的上下文
AI 不了解你团队的能力边界、你的技术债务情况、你的业务约束。这些内容需要人来补充。
第五步:最终确认
我过一遍,确认所有内容准确,保存。
Before/After 案例
这是一个真实的 ADR,用来记录"选择哪种 ID 生成方案"这个决策。
Before(我以前的写法):
# ADR-0012: 使用雪花算法生成 ID
状态:已接受
日期:2023-09-20
## 决策
使用雪花算法(Snowflake)生成分布式唯一 ID。
## 原因
数据库自增 ID 在分布式环境下有问题,用雪花算法解决。
## 影响
ID 格式变为 64 位整数,前端需要用 String 类型接收。这个 ADR 有结论,没有过程。6 个月后看,不知道:
- 当时到底有什么问题
- 考虑过哪些其他方案
- 为什么不用 UUID 等其他方案
- 决策是否有已知的坏处
After(用 AI 辅助写的版本):
我给 AI 的 brain dump:
我们在做一个新的订单系统,需要决定用什么 ID 生成方案。
背景:老系统用的数据库自增 ID,但现在要分库分表,自增不够用了。
讨论了这几个选项:
1. UUID:简单,但太长了,存储和索引都不太好,而且无序,B+树索引会碎片化
2. 雪花算法:Twitter 出的,64位整数,有序,包含时间戳,比较流行
3. 百度的 UidGenerator:基于雪花算法改的,解决了时钟回拨问题
4. 数据库号段模式(美团 Leaf):集中发号,不依赖本地时钟
最后选了雪花算法,原因是实现简单,团队熟悉,性能够用(每毫秒可以生成 4096 个 ID)。
已知的坏处是时钟回拨会出问题,但我们的服务器用 NTP 同步,实际触发概率很低。
还有个问题:雪花 ID 是 64 位整数,比 JavaScript 的安全整数范围大,前端拿到之后会精度丢失,需要前端用 String 接收。AI 整理后的 ADR:
# ADR-0012: ID 生成方案选型——采用雪花算法(Snowflake)
**状态:** 已接受
**日期:** 2023-09-20
**决策人:** 老张
---
## 背景
随着订单系统从单库单表演进到分库分表架构,原有的数据库自增 ID 方案不再适用:
- 自增 ID 在多个数据库节点上会产生 ID 冲突
- 跨分片的记录无法通过 ID 排序还原写入顺序
需要一个在分布式环境下生成全局唯一、且具有一定有序性的 ID 方案。
## 考虑过的方案
| 方案 | 优点 | 缺点 |
|------|------|------|
| 数据库自增(现有方案) | 简单、有序 | 不适用于分布式;单点瓶颈 |
| UUID v4 | 无依赖、实现简单 | 128位存储开销大;随机无序,B+树索引碎片化严重;可读性差 |
| 雪花算法(Snowflake) | 64位有序整数;高性能(4096/ms/节点);包含时间戳信息 | 依赖服务器本地时钟;时钟回拨会导致 ID 重复 |
| 百度 UidGenerator | 基于雪花算法,解决了时钟回拨问题 | 引入额外依赖;团队不熟悉 |
| 美团 Leaf(号段模式) | 不依赖本地时钟;可靠性高 | 需要独立部署发号服务;增加运维复杂度 |
## 决策
**采用雪花算法(Snowflake)。**
选择理由:
1. 64 位整数,可直接作为 MySQL BIGINT 主键,存储效率和索引性能优于 UUID
2. 时间戳前缀保证单调递增,B+树索引友好
3. 实现简单,团队已有使用经验,引入风险低
4. 当前系统规模(预计峰值 < 200 TPS/节点)远低于雪花算法上限,性能充足
## 已知风险和缓解措施
**风险1:时钟回拨导致 ID 冲突**
- 发生条件:服务器时间被回调(NTP 同步、手动调整等)
- 概率评估:当前服务器用 NTP 同步,在 100ms 以内的回拨不触发问题(已有保护机制),更大的回拨在生产环境中极少发生
- 缓解措施:实现里加入时钟回拨检测,回拨超过 5ms 时抛出异常,由运维人工介入
- 未来迁移路径:如果业务规模大幅增长,可以迁移到 UidGenerator(接口兼容)
**风险2:前端 JavaScript 整数精度丢失**
- 原因:雪花 ID 可能超过 JavaScript 安全整数范围(2^53 - 1)
- **必须处理:** 所有返回 ID 的接口,必须将 ID 序列化为 String 类型(而非 Number),前端对应接收类型改为 String
- 检查清单:已确认 Jackson 配置添加 `WRITE_NUMBERS_AS_STRINGS` 或针对 ID 字段单独加 `@JsonSerialize(using = ToStringSerializer.class)`
## 实施检查点
- [ ] 雪花算法实现(WorkerID 分配方案需要确认)
- [ ] 所有接口 ID 字段改为 String 序列化
- [ ] 前端字段类型确认
- [ ] 时钟回拨监控报警配置对比两个版本,差距是显著的。After 版本里的"已知风险"部分,特别是前端精度丢失的问题,是 AI 在整理草稿时提出的——我在 brain dump 里提到了,但只是一句话带过,AI 把它升级为了一个"必须处理"的风险,并给出了具体的检查项。
AI 发现的我没想到的备选方案
在第三步(让 AI 挑战决策)时,AI 提出了一个我之前没考虑的备选方案:
还有一个可能值得考虑的方案:ULID(Universally Unique Lexicographically Sortable Identifier)
ULID 是 128 位,和 UUID 大小相同,但:
- 前 48 位是毫秒时间戳,保证时间有序
- 后 80 位是随机数
- 字符串表示只有 26 个字符(vs UUID 的 36 个字符)
- 不依赖机器 ID,无需分配 WorkerID
如果你的系统以 API 为主,ID 更多出现在 URL 和 JSON 里而非数据库 JOIN,ULID 的可读性可能更有优势。
如果你的系统需要高效的数据库 JOIN 和范围查询,Snowflake 依然是更好的选择。我后来把这个备选方案加进了 ADR,即使最终没有选,这个记录也有价值——下次有人问"为什么不用 ULID"时,ADR 里有答案。
对团队文化的影响
说一件意外的事。
用 AI 写 ADR 这件事,让我们团队写 ADR 的频率提高了。以前一个季度可能写 2-3 篇,现在差不多每两周有一篇。
原因很简单:以前写 ADR 要花 2-3 小时,很多人觉得不值得,就跳过了。现在用 AI 辅助,30-45 分钟能搞定一篇质量不错的 ADR,门槛降低,执行率就上来了。
另一个变化:新人 onboarding 时有东西可以看了。
一个新工程师加入,我直接把过去一年的 ADR 目录发给他,让他先读。他说,读完这些 ADR 让他理解系统设计的速度,比看代码快多了。"我知道为什么这里用 Kafka 而不是 RabbitMQ,为什么这里用雪花 ID 而不是 UUID,这些在代码里看不出来。"
这是我用 AI 写 ADR 之前没有预想到的收获。
一个坦诚的说明
AI 不会替你做决策。ADR 里最核心的内容——"当时的背景是什么""考虑了哪些约束""为什么这个方案更合适"——这些只有你知道,AI 整理不出来凭空没有的信息。
AI 帮你做的是:把你的散乱思路整理成结构、提出你可能忽视的维度、让你更容易把这件事做完。
但做不做、写什么,决策还在人这里。
