我不是代码作者,我是 AI 的"包工头"——一个中型项目的代码重构实录

发布于 阅读 470未分类

写在前面

我是运维,为了日常管理方便,写了一个代号 Snow 的运维工具。4 年时间,它从几个 CRUD 接口长成了 10 大子系统的中台:账号、主机、进程、流水线、备份、调度、告警……期间换过 3 次架构、删过 2 次重写、引入过 1 个未完工的"DDD 改造"。

最近因为买了 MiniMax 的 Token Plan 套餐(好友订阅享9折+Builder权益),用不完心里不爽,加上对 Claude Code 有了一定的信心,我决定做一次彻底的重构。

这篇文章不讲"如何用 AI 写代码"的鸡汤,讲一个具体的、从 MVC 半吊子到彻底 DDD 的中型 Go 项目,怎么用 AI 一次性重构到位的完整工程实践。重点回答 3 个问题:

  1. 一个已经有"乱象"但又没乱到不可救药的中型 Go 项目,怎么用 AI 协作完成彻底的 DDD 四层重构?
  2. AI 写代码和人类写代码,在工程节奏上的根本差异是什么?如何用流程弥补?
  3. 当上下文窗口塞不下整个项目时,怎么用"文档体系 + 交接单 + 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 条不可妥协的标准,写进项目规则,违一条就拒绝:

  1. DDD 四层 + 10 个上下文彻底解耦
  2. 移除 3 个停更三方包,全部自实现
  3. 10 条工程性标准(CS-1 ~ CS-10)机器可校验
  4. 核心包测试覆盖率 ≥ 60%
  5. Makefile + lint + 凭据扫描 + CI 全套
  6. Prometheus 指标 + 审计日志 + requestID 全链路串联
  7. 灰度切换 + 秒级回滚

目标一旦清晰,剩下的就是"怎么把项目拆到 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 我的解法:局部全貌代替整体全貌

解法分三层:

  1. 拆解:把 4 万行拆成 12 阶段、11 个执行窗口,每块 ≤ 2000 行,装得进一次会话
  2. 模板化:8 个共享代码模板,AI 不需要"猜"怎么写,复制就行
  3. 交接单: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-3RequestID 唯一来源 + 脱敏check_no_hardcoded_id.sh
CS-4Timeout 5 档统一check_no_hardcoded_timeout.sh
CS-5DTO 共享模板 + entity 零 tagcheck_entity_no_gorm_tag.sh
CS-6DI 容器统一,main ≤ 200 行check_main_size.sh
CS-7测试基线 60% + gomock/testifymake coverage
CS-8CI 工具链make lint
CS-9DDD 4 层 + legacy 全部清空check_no_legacy.sh
CS-10可观测性(Metrics + Audit)/metricssnow_*

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 改

每个阶段完工后的标准循环:

  1. 基于 spec 写本阶段的用例
  2. AI 跑测试
  3. AI 读报告,定位失败
  4. 改后端代码或改用例期望值(看是 bug 还是期望写错了)
  5. 再跑一次
  6. 重复直到通过率达标
  7. 人扫一眼总通过率,确认进入下一阶段

"先写应有行为,跑通后改现状"的工作流,比"先看现状再写期望"更稳——它强迫把"应有行为"和"现状"分开,review 时一眼看到 V2 哪里和 V1 不一致。

7.5 实测数据

阶段用例数首次 fail最终真实 bug
P0 基础设施404/40
P1 账号19619/192
P2 标签20520/201
P3 主机30830/303
P4 进程控制221122/225
P5 监控404/40
P6 通知828/81
P7 告警接入616/60
P8 流水线616/61
P9 调度13713/130
合计12641126/12613

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 的解法是三件套:

  1. 调用方定义 Port 接口(只声明"我需要什么")
  2. 提供方实现 Adapter(把本 ctx 的 service 适配成 Port 签名)
  3. 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 代码",而是:

  1. 一套可机检的工程标准 —— 让"规范"不再是文档,PR 时强制执行
  2. 一套上下文传递机制 —— 让新会话 30 分钟就能稳定接上
  3. 一套 AI 协助下的增量验证体系 —— 让"完工"有客观标准
  4. 一套 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 重构完结,准备进入灰度期

评论 (0)
0 / 500
暂无评论,快来抢沙发~