写在前面
我是运维,为了日常管理方便,写了一个代号 Snow 的运维工具。4 年时间,它从几个 CRUD 接口长成了 10 大子系统的中台:账号、主机、进程、流水线、备份、调度、告警……期间换过 3 次架构、删过 2 次重写、引入过 1 个未完工的"DDD 改造"。
最近因为买了 MiniMax 的 Token Plan 套餐(好友订阅享9折+Builder权益),用不完心里不爽,加上对 Claude Code 有了一定的信心,我决定做一次彻底的重构。
这篇文章不讲"如何用 AI 写代码"的鸡汤,讲一个具体的、从 MVC 半吊子到彻底 DDD 的中型 Go 项目,怎么用 AI 一次性重构到位的完整工程实践。重点回答 3 个问题:
- 一个已经有"乱象"但又没乱到不可救药的中型 Go 项目,怎么用 AI 协作完成彻底的 DDD 四层重构?
- AI 写代码和人类写代码,在工程节奏上的根本差异是什么?如何用流程弥补?
- 当上下文窗口塞不下整个项目时,怎么用"文档体系 + 交接单 + e2e 体系"把上下文切碎、传递、复用?
1. 它是怎么"烂"成现在这样的
要彻底重构,先得搞明白它是怎么一步步走到今天的。
1.1 MVC 起步:没毛病,但长歪了
2022 年,Snow 最初是个 3000 行的 Gin 单体后端,标准 MVC:controller → service → model → dao。MVC 在小项目下没毛病,我博客现在也是 MVC。
问题是接下来两年,业务从 3000 行涨到 6 万行,service 层膨胀到 1.2 万行。任何改动都要碰 service,任何 service 改动都要看 controller 和 dao,改一次"心率加速半小时"。
service 层变成了"万能层"——数据库访问、缓存、消息发送、外部 HTTP、gRPC 调用全堆在里面。文件动辄 800 行,5 个 service 之间互相 import,改一行查 8 个调用点。错误处理风格也分裂成三套,前端只能笼统地显示"出错了"。
1.2 半吊子 DDD:比不改更危险
2024 年我决定"按 DDD 整改",但这次整改没有彻底:
- ✅ 做了:目录结构按 DDD 重排(加了 domain / application / infrastructure 目录名)
- ❌ 没做:代码没真的按 DDD 写,service 还在"做一切",controller 直接 import 底层 dao
- ❌ 没做:没机器校验,是不是 DDD 全靠 code review,但忙起来 review 就是走过场
- ❌ 没做:没测试,以为 DDD 会自然带来好测试,结果目录换了,代码没换
半吊子 DDD 的最大副作用是"假合规":目录看起来像 DDD,乍一看"已经是 DDD 了",不再追问。高手知道不是真的,但没有动力改,也改不动。
1.3 触发点:这次为什么不一样
之前不动,是因为 AI 写完代码容易,但一个个接口调试和修改太麻烦。直到前段时间,我在博客项目上用测试 skill 取得了成功(AI 写代码的「修一炸三」老毛病,被我自己造的一个 skill 治了),对 AI 辅助重构有了信心。
决定:这次彻底重做,代号 V2。4 年前的"半吊子 DDD"全部推翻,从脚手架开始重写。
2. V1 到底"病"在哪
动手之前,我做了一次逐文件审计,把 V1 的乱象摸清楚:
| 维度 | 现状 | 后果 |
|---|---|---|
| 8 个上下文全部直接 import 底层 dao | 无任何隔离 | 改一个 dao,7 个上下文都可能崩 |
| 错误处理 3 套风格并存 | 业务码无法传递 | 前端没法精确报错 |
| 3 套 requestID 机制 | 全局变量 / ctx / gin 各自存 | 排查一个请求要 grep 几十分钟 |
context.Background() 大量滥用 | 8+ 处硬编码超时 | 长任务卡死 goroutine |
| 测试覆盖率 < 5% | 几乎没单测 | 改一行就怕 |
| 配置 73 个散点 KV | 改一个配置找半天 | 谁都记不住路径 |
| 3 个第三方包 5+ 年未更新 | 进程控制 / XML-RPC / 工作流引擎 | 修不了 bug |
这是典型的"项目改一段时间后乱成一团"综合症。所有症状的根因只有一个:没有任何机制能阻止"乱"。人盯人,盯不住;改一次就退步一次。
3. 重构的硬目标
明确了 7 条不可妥协的标准,写进项目规则,违一条就拒绝:
- DDD 四层 + 10 个上下文彻底解耦
- 移除 3 个停更三方包,全部自实现
- 10 条工程性标准(CS-1 ~ CS-10)机器可校验
- 核心包测试覆盖率 ≥ 60%
- Makefile + lint + 凭据扫描 + CI 全套
- Prometheus 指标 + 审计日志 + requestID 全链路串联
- 灰度切换 + 秒级回滚
目标一旦清晰,剩下的就是"怎么把项目拆到 AI 能稳定执行的颗粒度"。这是整个重构最核心的工程问题。
4. 核心难题:为什么必须把项目拆碎给 AI
这一节是整个重构思路的"灵魂"。
4.1 上下文窗口的天花板
无论用什么 LLM,单次会话的上下文窗口是有限的。4 万行 Go 代码 + 14 张表 + 60+ API + 10 条标准,塞不进一次会话。即使塞进去了,也会"挤出"早期的指令,导致 AI 写到后面"忘了前面说啥"。
4.2 比"塞不下"更严重的是"信心丢失"
我在之前的项目里发现一个规律:
AI 写代码的"信心"完全来自上下文窗口里装下的"项目全貌"。装得越全,写得越对;窗口越空,写得越飘。
信心足时,AI 知道每个接口的签名、返回值类型、错误码——写出来的代码一次过。信心丢失时,AI 会"幻觉"一个不存在的接口,编出一个错误的错误码,review 的人如果不熟悉整个项目,根本看不出来。
AI 写错代码,你能 review 出来;AI 信心丢失时,它会编造看起来合理但实际错误的东西,这才是最危险的。
重构项目要解决的核心工程问题,就是把"信心"装回 AI 的上下文窗口。
4.3 我的解法:局部全貌代替整体全貌
解法分三层:
- 拆解:把 4 万行拆成 12 阶段、11 个执行窗口,每块 ≤ 2000 行,装得进一次会话
- 模板化:8 个共享代码模板,AI 不需要"猜"怎么写,复制就行
- 交接单:11 份 handoff 把"上一块的关键信息"打包好,下一个 AI 会话 30 分钟内接上
这套机制的本质是"局部全貌代替整体全貌"。AI 在每个窗口里看到的"局部全貌"足够它"局部正确";多个"局部正确"通过交接单串成"整体正确"。
如果只能从这次重构带走一个心得,我会选这句:AI 写代码的最大成本不是"写"本身,是"信心的保持"。把项目拆碎 + 模板化 + 交接单,是当前阶段保持信心的唯一工程化方案。
5. 怎么拆:11 个窗口、12 阶段、10 条标准
5.1 阶段切分
把 4 万行目标代码拆成 12 个阶段,每个阶段只解决一类问题,前后依赖明确:
P0 工程脚手架 → Go Module + 10 跨 ctx 子系统 + 8 check 脚本 + Makefile P1 账号(Accounts) → 用户账户 4 层 + 8 API P2 标签(Tags) → 标签 4 层 + 5 API P3 主机(Hosts) → 主机 4 层 + 7 API + 跨 ctx Port 模板 P4 进程控制 → 自实现 22 个 XML-RPC + 12 API P5 监控查询 → 指标查询 + 4 API P6 通知(Notify) → 即时通讯双通道 + 5 API P7 告警接入 → 告警入口 + 复用 NotifyPort P8 流水线 → 自实现工作流引擎 + 4 查询 API P9 调度(Scheduler) → Cron + 自实现分布式锁 + 10 API P10 备份(Backup) → RPC + 对象存储 + 20+ API P11 集成与清理 → 路由对齐 + 灰度切换 + 删旧目录
依赖图是清晰的有向无环图:P4/P5/P6/P8/P9 互不依赖可以并行;P3/P7/P10 各自等前置;P11 等所有。
5.2 窗口化执行
把 12 个阶段映射到 11 个执行窗口,单人顺序执行,实际只花了 3 天,全部交给 AI 执行,一个阶段完成了开新 session 执行下一个。
5.3 10 条工程性标准(CS-1 ~ CS-10)
这是 V2 与 V1 的根本差异。每条标准都有代码模板 + check 脚本 + 验收标准:
| 编号 | 名称 | 强制门禁 |
|---|---|---|
| CS-1 | 跨上下文必须走 Port 接口 | check_cross_context.sh |
| CS-2 | 统一业务错误 + 业务码 | check_errors.sh |
| CS-3 | RequestID 唯一来源 + 脱敏 | check_no_hardcoded_id.sh |
| CS-4 | Timeout 5 档统一 | check_no_hardcoded_timeout.sh |
| CS-5 | DTO 共享模板 + entity 零 tag | check_entity_no_gorm_tag.sh |
| CS-6 | DI 容器统一,main ≤ 200 行 | check_main_size.sh |
| CS-7 | 测试基线 60% + gomock/testify | make coverage |
| CS-8 | CI 工具链 | make lint |
| CS-9 | DDD 4 层 + legacy 全部清空 | check_no_legacy.sh |
| CS-10 | 可观测性(Metrics + Audit) | /metrics 含 snow_* |
check_*.sh 脚本才是真正的防线。 每条标准对应一段静态扫描,违反直接 fail。这套机制对 AI 写代码的约束力尤其大——AI 写 100 行,80% 的"AI 味反模式"会被 check 脚本当场拦下,而不是 review 时才发现。
我准备在项目重构完成后,把这些脚本形成一个项目级skill,每次修改后,检查一遍,违规就警告,重新修改。
6. 上下文工程:AI 大型项目执行的核心难题
6.1 文档金字塔
我建了 5 层文档,从总览到细节,按需加载:
顶层 CLAUDE.md(项目入口) └─ V2 CLAUDE.md(宪法:10 条标准 + 强约束) └─ REFACTORING_PLAN.md(主索引:12 阶段 + 交付清单) ├─ EXECUTION_MANIFEST.md(执行清单:11 窗口 + SOP) │ └─ W<n>_handoff.md(本窗口状态,7 节固定格式) ├─ phases/P0~P11.md(每阶段实施步骤) └─ cs_templates.md(8 个代码模板,所有 ctx 复用)
关键设计:主索引控制在 350 行内,只装"门 + 索引",不装"内容";共享模板是代码的唯一来源,禁止 AI 自由发挥。
6.2 交接单:窗口间的"上下文接力棒"
每完成一个窗口,写一份 7 节固定格式的交接单:状态、关键技术决策、产出文件清单、端到端验收、已知遗留、业务码段、下个窗口起步清单。
新窗口开始时,AI 不需要重读整个项目,只需要读 3 份文档——执行清单、上一份交接单、本阶段 spec。总共 30 分钟上下文加载,然后就能稳定开始干活。
6.3 每个窗口的固定 SOP
T+0 读执行清单(确认范围) T+1 读上一份交接单(拿交付状态) T+2 读本阶段 spec(实施细节) T+3 实施(按文档 checklist) T+4 跑 make check + smoke test T+5 git commit T+6 写本窗口交接单
这套 SOP 对 AI 协作极其重要——每次新开会话,只需要一句"按 W<n> SOP 开始",AI 就会自动读 3 份文档,自己接上窗口,不需要我重复交代背景。
7. AI 协助下的 e2e 体系:增量验证的核心机制
这里的"e2e"和传统 e2e 不是一个东西。
传统 e2e:QA 写脚本、人工跑测试、人工看报告、人工提 bug。 本文 e2e:AI 的 skill + 126 个 YAML 用例 + AI 自己跑、自己看报告、自己定位失败、自己改后端代码。
人只在"信任门"上出现——扫一眼总通过率 97.6%,确认可以继续。
7.1 为什么需要这套体系
重构到 P5 时,我想验证:Prometheus 真的连得上,告警 webhook 真的发到钉钉/企微了,进程控制真的改状态了。
如果用单元测试,测不到 80% 的关键路径——mock 掉了 HTTP 层、gRPC、Consul、钉钉 SDK,跑单测全过,但生产一上线就 500。
7.2 体系结构
Skill 层(AI 看的):Claude Code 里的 /api-test 命令,触发整个测试循环。
执行层(机器跑的):
run.sh:一键入口——编译 V2 → 启测试实例 → 清表 → 跑用例 → 出报告 → 关后端run_tests.py:编排器(400 行,只依赖 requests + pyyaml)cases.yaml:126 个用例,10 个 phase- HTML 报告生成器
协同关系:人输入 /api-test → AI 调 run.sh → 跑用例 → AI 读报告 → 定位失败 → 改后端代码 → 重跑 → 直到通过率达标。人只扫一眼最终通过率。
7.3 链式编排:占位符 + 副作用
cases.yaml 用 {{key}} 占位符从 state.json 取值,上一条用例的响应字段写入 state,下一条通过占位符引用,形成链式编排。126 个 case 自动形成依赖图,不需要写 fixture,不需要 setup/teardown。
比如主机模块:建 host → save id → 后续查询/修改/绑标签全部引用这个 id,排成线性剧本,跑通就等于这一阶段 90% 的接口行为都验过了。
7.4 AI 跑 → AI 看 → AI 改
每个阶段完工后的标准循环:
- 基于 spec 写本阶段的用例
- AI 跑测试
- AI 读报告,定位失败
- 改后端代码或改用例期望值(看是 bug 还是期望写错了)
- 再跑一次
- 重复直到通过率达标
- 人扫一眼总通过率,确认进入下一阶段
"先写应有行为,跑通后改现状"的工作流,比"先看现状再写期望"更稳——它强迫把"应有行为"和"现状"分开,review 时一眼看到 V2 哪里和 V1 不一致。
7.5 实测数据
| 阶段 | 用例数 | 首次 fail | 最终 | 真实 bug |
|---|---|---|---|---|
| P0 基础设施 | 4 | 0 | 4/4 | 0 |
| P1 账号 | 19 | 6 | 19/19 | 2 |
| P2 标签 | 20 | 5 | 20/20 | 1 |
| P3 主机 | 30 | 8 | 30/30 | 3 |
| P4 进程控制 | 22 | 11 | 22/22 | 5 |
| P5 监控 | 4 | 0 | 4/4 | 0 |
| P6 通知 | 8 | 2 | 8/8 | 1 |
| P7 告警接入 | 6 | 1 | 6/6 | 0 |
| P8 流水线 | 6 | 1 | 6/6 | 1 |
| P9 调度 | 13 | 7 | 13/13 | 0 |
| 合计 | 126 | 41 | 126/126 | 13 |
41 个初次 fail 中,13 个是真后端 bug,28 个是期望值写得太理想。这 13 个真 bug 如果没有 e2e 验证,会一直活到生产。
8. AI 写代码的 4 个特点与工程化应对
11 天 V2 重构,100% 代码是 AI 写的。过程里我总结了 AI 写代码的 4 个特点,以及对应的工程化应对。
特点 1:写"看起来对"的代码速度极快
让 AI 写一个 CRUD 接口,5 分钟产出 200 行,看上去完美。但仔细看:entity 上带了不该有的 tag、handler 里错误处理不合规、service 缺了 ctx 入参……
应对:不要逐行 review AI 输出的代码,要 review check 脚本的输出。make lint 30 秒出结果,告诉我哪些行违反哪条标准。AI 写代码最大的成本是 review,check 脚本是 review 的最佳预处理器。
特点 2:不知道"项目里其他文件怎么用"
AI 在 P3 写 service 时需要查 tag 是否存在,它会直接 import 另一个上下文的 dao。
应对:check_cross_context.sh 静态扫所有 import 路径,机器拦截比 AI "自觉"可靠 100 倍。AI 不是不聪明,是它的上下文窗口只够看到当前阶段的文档。
特点 3:写的代码是"孤立的",没有"链"
AI 写完一个上下文,跑 5 个单测全过,它会认为"写完了"。但实际跑 e2e 时,跨上下文调用可能签名对不上。
应对:e2e 体系的链式用例是这种问题的"金丝雀"。第一个跨上下文的用例就能发现接口签名不匹配、错误码不对应。
特点 4:AI 不需要"上下文交接",但项目需要
每天 3-5 个新会话,AI 不会主动告诉我"上个阶段有 2 个已知 bug 没修"。
应对:交接单是 7 节固定格式的"上下文交接单",新会话读上一份交接单,自动知道遗留问题和关键文件位置。
9. 实施过程中几个值得讲的决策
自实现替代三方包
V2 自实现了 3 个停更的第三方包:22 个 XML-RPC 方法(350 行)、80 行工作流引擎、分布式锁(Redis SETNX + Lua 释放 + 看门狗续期)。
当三方包"用得少"且"自实现代码量小于三方包"时,自实现永远更优。 这次实战验证了这个判断。
跨上下文通信的 Port 模式
hosts 需要"查 tag 是否存在",但不应该直接 import tags 的 dao。V2 的解法是三件套:
- 调用方定义 Port 接口(只声明"我需要什么")
- 提供方实现 Adapter(把本 ctx 的 service 适配成 Port 签名)
- DI 装配 + 编译期断言(写错就编译不过)
这让"跨上下文解耦"从文档约束变成了机器可校验的硬约束。
P11 发现的最重要 bug
集成阶段发现 cases.yaml 顶层有 2 个 phases: 键,YAML 后者覆盖前者,导致 P0-P8 共 113 个 case 实际从未执行过。这种 bug 单元测试发现不了,只有 e2e 全量回归才能暴露。
10. 方法论总结:4 个可复用的工程实践
实践 1:文档金字塔 + 交接单接力
问题:大项目上下文塞不下单一会话。
解法:5 层文档金字塔 + 每阶段 1 份固定格式交接单 + 新会话读 3 份文档就能接上。
效果:11 个窗口 11 份交接单,没一次因为"忘了上下文"而返工。
实践 2:CS 标准 + check 脚本门禁
问题:AI 写的代码"看着对但不合规"。
解法:10 条标准对应 8 个静态扫描脚本,PR 时强制运行,违反则红。
效果:实施期间累计拦截 200+ 处违规,平均每阶段 15-20 处。
实践 3:AI 协助下的 e2e 体系
问题:重构到一半不知道"现有部分是不是还能用"。
解法:126 个 YAML 用例 + /api-test skill + 链式编排 + AI 自跑自改。
效果:每个阶段完工时 90% API 行为已验证,测试-诊断-修复循环几乎全自动。
实践 4:灰度切换 + 秒级回滚
问题:V2 一次性切流量风险大。
解法:数据库物理隔离 + 灰度 SOP(1 台 → 7 天观察 → 逐台扩)+ 5 条判停标准 + V1 二进制保留回滚。
11. 写在最后
这次 V2 重构的真正成果不是"2.4 万行新 Go 代码",而是:
- 一套可机检的工程标准 —— 让"规范"不再是文档,PR 时强制执行
- 一套上下文传递机制 —— 让新会话 30 分钟就能稳定接上
- 一套 AI 协助下的增量验证体系 —— 让"完工"有客观标准
- 一套 AI 协作节奏 —— 让 AI 知道"现在该做什么、下一步该交付什么"
AI 写代码不是难题,让 AI 写"对的"代码才是难题。 这次重构中 AI 写了 100% 的代码,我一行 Go 都没手写,但项目不是"AI 写的"——是 AI 在 8 个 check 脚本的约束下、在 11 个窗口的节奏里、在 126 个 e2e 用例的验证下产出的代码。我的角色是"规则设计者 + 信任门守门人",不是"代码作者"。
回顾 4 年,3 个不后悔的判断:
- 2022 年用 MVC 起步没毛病 —— 业务量小的时候,MVC 是合适的,过度设计才是浪费
- 2024 年"半吊子 DDD"是有价值的失败 —— 让我知道 DDD 不是"改个目录名",而是"改一整套心智模型 + 工具链"
- 2026 年用 AI 做彻底 DDD 是值得的 —— AI 能稳定执行的前提是工程化的护栏体系,这套机制本身比"AI 写代码"更有价值
工程的本质不是写代码,是在代码和人/AI 之间,架设一套"让错误不可能发生"的护栏。
日期:2026-06-10 项目代号:Snow 版本:V2 重构完结,准备进入灰度期
