过去这一年,我最大的感受不是“AI 写代码有多快”,而是“我们代码仓库里的 Merge 动作变得越来越沉重”。AI 编程工具——不管是 GitHub Copilot、Cursor,还是我自费订阅的 Codex 这类付费 AI 编程软件——确实把“生成代码”的门槛按到了地板砖以下:你只要会说人话,把需求描述得稍微清楚一点,一分钟内就能拿到一段像模像样的函数,甚至是一个完整服务。可奇怪的是,仓库里 Merge 按钮被按下的频率反而变低了,或者说,按下去之前犹豫的时间变长了。
为什么?因为门槛降低的是“写出代码”,而 Merge 意味着你要对这段代码负责。写代码的爽感是即时的,Merge 的代价却是长期的。这篇文章我想好好聊聊这个现象:AI 编程狂飙之后,真正卡住我们的不是写不出来,而是不敢合、合不动、合完心里没底。我会把技术层面的冲突处理、工具选择、流程设计,以及心理层面的信任问题,一次说透。
1. AI 编程狂飙:写代码的门槛确实接近零了
1.1 我一天“写”出了以前一周的代码量
先说个真实场景。前阵子我接到一个任务:把一套内部日志系统从单体脚本改造成分批处理的管道服务。放在以前,这个活至少要排一周——要理清数据格式、写解析器、设计线程池、处理失败重试,还要补单元测试。这次我直接打开 Cursor,把需求拆成五段提示词,一段一段喂进去,包括“输入格式如下”“输出要符合现有日志规范”“失败重试用指数退避”。AI 在半小时内给出了全部核心代码,我复制进项目,改了几处类型不匹配,跑通了本地测试。
这个效率确实吓人。代码库里的产出速度肉眼可见地翻了几倍,团队里几个刚转行的小伙子甚至能借助 AI 独立完成从前需要两年经验才能扛住的模块。但问题也从这一刻开始冒头了:代码是有了,可仓库里的冲突、回退、返工也同步变多。我检查了一下最近的 PR 数据,AI 辅助产出的 PR 平均 diff 行数是人工手写的三倍左右,而“引入新问题后被打回”的比例也明显上升。写代码的瓶颈确实消失了,但瓶颈没有消失,它只是往后移动,移动到了 Merge 这一步。
1.2 “生成”不等于“完成”:AI 编程的真实工作流
现在做开发的工作流已经不是“需求 -> 写代码 -> 提交”,而是“需求 -> 写提示词(ai 编程提示词成了新技能)-> 生成代码 -> 集成 -> 审查 -> 合并”。真正花时间的环节从“写”转移到了“验证和集成”。说白了,AI 帮你把代码生产出来了,但代码要融入现有代码库,要经过编译、测试、静态检查、同行 review,最后合入主干——这后面每一步,都在替 AI 的产出“验货”。
我试过最顺的一次,是让 AI 生成一个完全独立的工具函数,不依赖任何现有模块,从生成到合入只花了二十分钟。但一旦涉及修改老代码、牵动多个调用方,或者跟别人的分支并行改动同一个文件,事情就完全不是那个味道了。生成是 AI 的,合并是你自己的——这句话我最近常跟团队说。AI 生成的每一行代码,最后都是签着你的名字进主干的,出了问题,Code Review 不会去找 AI 问责,只会来找你。
2. 为什么 Merge 成了新的“卡脖子”环节
2.1 代码写得快,合并却变慢了
这是个非常反直觉的现象:整体工作量明明在减少,Merge 的耗时却翻了倍。我统计过自己最近两个月的数据,一个中等规模的 AI 辅助功能,从“代码生成完成”到“成功合入主干”,平均要花掉原本手工开发 40% 的时间。而纯手工时代,这个比例连 20% 都不到。
原因其实不复杂。第一,AI 生成的代码“块头大”。它倾向于一次性丢出一个完整模块,而不是像人那样拆成几个语义清晰的 commit。第二,AI 喜欢“顺手整理”,生成代码时经常把文件里的 import 顺序重排、把双引号改成单引号、把函数声明顺序打乱——这些不是功能变化,但都会在 diff 里制造大量噪音。第三,AI 不擅长做“最小变更”。你跟它说“给这个函数加个边界判断”,它往往会把整个函数重写一遍,逻辑对了没错,但本来 5 行的 diff 变成了 80 行。
这直接导致了一个后果:Code Review 的负担爆炸。人眼逐行审阅 80 行里只有 5 行真正变化的代码,效率极低,而且很容易漏掉 AI 偷偷改出来的行为差异。Merge 前必须 review,review 变慢,Merge 自然就慢了。
2.2 不敢 Merge 的本质:信任与责任问题
技术层面的“慢”还不是最可怕的,最可怕的是心理层面的“不敢”。我自己就有这种体验:面对一大段 AI 生成的代码,运行起来好像没问题,测试也过了,但我就是不敢点 Merge。因为我不知道这段代码的“意图”。人工写的代码,我知道作者在想什么,我知道他为什么这么命名、为什么这么兜底;AI 写的代码,我看到的只是一堆“看起来合理”的逻辑,它为什么加这个空指针判断?为什么 catch 了这个异常却不处理?这些问号在 review 阶段根本没办法验证,只能靠猜。
这就是信任问题。Git 的 Merge 操作在语义上代表“我确认这个改动可以进入主干”,它本质上是一个承诺。而人很难对自己不理解的东西做出承诺。再加上万一合出生产事故,责任是实打实落到你头上的——AI 不会替你背锅。我之前有个同事,用 AI 生成了一段处理货币格式化的小工具,本地测得好好的,合入主干后在特定语言环境下直接炸了。事后排查发现,AI 用了一个只支持部分 locale 的 API,而这个 API 在他的机器上跑不出来边界问题。这个教训过后,他每次 review AI 代码都带着“先怀疑,后相信”的劲头。我觉得这种感觉会越来越普遍:不是 AI 能力不行,而是人类审查者天然对自己没参与过的东西缺乏安全感。
3. 核心细节:AI 时代的 Merge 困境拆解
3.1 冲突从哪来:AI 的“改写习惯”与人不一样
Merge 冲突的本质是两个分支改了同一处代码。人工时代,大家改代码都比较克制,有冲突也往往是真正正交的业务改动。AI 时代不一样,AI 的“改写习惯”导致冲突概率显著上升。具体来说有三种典型场景:
- 整块重写型:你和 AI 分别在两个分支上改同一个函数,AI 不会像人那样只加一行参数校验,它会把整个函数体重新生成。等到合并时,git 面对的是两个完全不同的函数版本,冲突范围从“几行”扩大到“整个函数”。
- 格式扰动型:AI 的生成模型对代码风格有“自己的审美”,它可能把双引号改成模板字符串,把 for 循环改成 forEach,甚至调整文件头注释的格式。这些改动和你的功能改动叠在一起,在合并时产生大量本不该存在的冲突。
- 重复实现型:AI 不读全局代码,你让它加一个“日期格式化”的功能,它可能不知道项目里已经存在一个
formatDate工具函数,于是又生成一个功能几乎一样的formatTime。两个分支都这么干,合并时就会出现“同一职责的重复代码”,冲突的不是文本,而是设计。
碰到这类情况,我的原则是:先看 git diff,再判断冲突的“性质”。如果冲突 80% 来自格式扰动,直接git checkout --ours/theirs选定一边,然后手动把真正需要的功能补进去;如果冲突来自整块重写,就别用合并工具硬拼了,手动把两个版本的核心逻辑抽出来,重新组织成一个干净的函数。
3.2 JSON merge conflict:语义冲突比文本冲突更可怕
这个我要单独讲,因为现在前后端分离的项目里,JSON 配置文件的冲突几乎成了 AI 协作时代的标配。拿一个最常见的场景举例。项目根目录有个config.json:
{ "features": { "auth": true } }你在分支 A 上加了一个功能开关,AI 在另一个分支 B 上也加了一个功能开关,两边修改的是同一个features对象。等合并时,git 的文本合并算法开始头疼:它在同一个位置看到两个不同的改动,于是报出 conflict。
但真正的难点在下一步。git 只会告诉你“同一个区块被改两次”,它不会告诉你两个改动其实可以兼容——一个加"payment": true,另一个加"notifications": true,这两个键合到一起才是正确结果。如果你用Accept Yours或Accept Theirs,搞不好就把对方的功能开关丢了,而且这种丢失在编译阶段完全不会被发现,只有运行期功能异常了才暴露。我在生产环境踩过这个坑:就是Accept Yours太顺手,把一个部署分支的功能开关给吞了,上线后那个功能毫无反应,排查了一下午。
所以现在我处理 JSON 冲突有个固定动作:先看冲突上下文,判断是“键级冲突”还是“值级冲突”。键级冲突(两边新增各自的新键)基本上手动合并就能解决,把两边的键都保留;值级冲突(同一个键两边给不同值)就麻烦些,得弄明白哪边的值才是当前需求的正确答案。另外建议给配置文件配上 JSON Schema 校验,CI 里加一步,能提前挡掉很多结构性错误。
3.3 Merge 还是 Rebase,这是个判断题
“merge rebase”这个搜索关键词能上热搜,说明大家都在纠结。AI 时代这个纠结更严重了,因为 AI 生成的 commit 往往又大又杂,直接让 rebase 变成一种折磨。
我个人的判断标准,用一张表说清楚:
| 场景 | 用 Merge | 用 Rebase |
|---|---|---|
| 公共主干分支(master/main) | 适合 | 不适合,会改写公共历史 |
| 长期功能分支(多人协作) | 适合 | 勉强,但冲突会很多 |
| 短期个人功能分支 | 可用 | 适合,历史更干净 |
| AI 生成的大型改动 | 强烈建议 | 非常痛苦,不建议 |
| 需要保留“合并上下文” | 适合 | 丢失上下文 |
具体到 AI 协作场景,我的默认方案是:AI 产出的 PR 一律走 Squash Merge。因为 AI 生成的 commit message 通常是一段“我新增了 XX 功能”式的流水账,不具备可追溯性。Squash 成一个 commit 后再合入主干,主干历史干净,回滚也容易。如果你真的很在意分支历史,也请先git rebase -i把 AI 的 commit 整理成几个语义清晰的节点,再碰合并操作。别拿原始 AI commit 直接上 rebase,那只会让自己陷入“崽卖爷田不心疼”的连环冲突地狱。
4. 实操:重新捡起“敢 Merge”的底气
4.1 从源头下手:让 AI 写出“可合并”的代码
与其等到冲突爆发再当消防员,不如在写提示词的时候就把“可合并性”作为必要条件。这是我反复调教出来的提示词模板,你可以直接抄:
请对我现有的函数 addUser 做最小化修改,追加一个 phone 字段的校验逻辑。 要求: 1. 只修改 addUser 函数内部,不要改动其他任何函数 2. 不要重排 import 顺序,不要修改代码格式 3. 不要重构文件内其他代码 4. 保持现有命名风格和代码风格 5. 输出时只描述你改了哪些行关键词是“最小化修改”和“不要动其他代码”。实测下来,加了这两句话之后,AI 产出的 diff 能从几十行缩到十几行,合并冲突概率明显下降。另一个技巧是拆任务:别让 AI 一次生成一个巨型功能,而是把它拆成“先加工具函数”“再改调用点”“最后补测试”三个独立提示词,分别提交。每个 commit 小一点,review 轻松一点,冲突面也跟着小。
4.2 合并前的“信任检查清单”
不敢 Merge,本质上是不敢信任。那就把信任建立成一套可执行的流程。我给自己定了这么一份合并前检查清单,每次合 AI 代码必须过一遍:
- 编译和测试:本地全量跑一遍构建,单测、集成测试一网打尽。重点跑新增代码涉及的所有模块的测试,包括那些看似无关的调用方。
- 静态检查:跑 linter 和类型检查。AI 生成代码最常见的雷就是用了不存在的 API、参数类型不匹配、空指针没兜底。
- 搜索“幻觉 API”:去官方文档或依赖库里搜一遍 AI 用的关键类和方法。AI 特别喜欢一本正经地编一个不存在的第三方库方法,这个检查能救你一命。
- 安全扫描:看有没有硬编码的密钥、Token、数据库连接串。AI 生成示例性质的配置时经常顺手塞进去假的密钥,合并到主干再被扫出来就是事故。
- 死代码清理:AI 生成的时候经常会留一堆没被调用的辅助函数、未使用的 import。合并前删掉,别让主干背着这些垃圾。
- 反向检查 diff:从整个 PR 的 diff 反推“这次改动到底想干什么”,如果推不出来,或者发现 diff 里混进了无关改动,就打回去重来。
这套清单看起来啰嗦,但实际操作一分钟就能过完前三项,真正花时间的只有第四和第六项。顺带说一句:把能自动化的都自动化——CI 门禁上挂编译、测试、lint、安全扫描,让机器把低级错误先挡在门外,人只在关键点上做判断。
4.3 冲突处理的实操手法:从回退到手工合并
冲突无法避免,但要学会全身而退。我实战中踩过最大的坑就是“硬解冲突”——在一个复杂的大冲突里死磕两小时,越改越乱,最后代码比以前还糟。所以现在我的原则是:超过 10 处冲突,或者冲突涉及核心模块,先撤退再进攻。
撤退的第一道命令是:
git merge --abort这条命令会把当前仓库状态恢复到触发 merge 之前,干净利落。如果你在合并过程中手动改了一部分文件,git merge --abort会把这部分改动也丢掉——所以如果你已经改了超过一半的冲突,就别 abort 了,硬着头皮走完,或者用git checkout -- .重置后重新处理。
在 IntelliJ IDEA 里操作更直观。执行 merge 后如果弹出冲突对话框,你可以选择 Accept Yours(保留当前分支版本)或 Accept Theirs(保留合并进来的分支版本),也可以打开 Three-Way Merge 面板手动编辑。如果你改着改着发现冲突太多了想放弃,只需要打开 VCS -> Git -> Merge Changes,在弹出的对话框里点 Abort 就行。很多人不知道 IDEA 里还有这个按钮,都是傻乎乎地把文件改得乱七八糟再git reset --hard。至于已经合并完成的 commit 想回退,IDEA 可以在 VCS 操作历史里找到这次 merge,执行 Revert Commits,终端方式则是找到 merge commit 后执行:
git revert -m 1 <merge-commit-id>-m 1指定保留主分支那一侧,把被合并进来的改动整体撤销。这是回退合并提交最安全的姿势。
手工合并 JSON 冲突时,我的做法是把冲突标记里的两段都复制出来,放到一个临时 JSON 文件里用格式化工具展开,然后手动决定哪些键保留、哪些值取谁。虽然慢一点,但不会丢配置。
4.4 让 AI 帮你 review AI:机器审机器,人做裁决
这是个实用到离谱的小技巧:既然 AI 代码量大,人工逐行看不过来,那就让第二波 AI 来做摘要和初筛。我现在常用两个模型搭档干活:一个生成代码,另一个负责 review。review 的提示词大概是这样的:
请审查以下 diff,重点检查: 1. 是否有未定义或错误的 API 调用 2. 是否有资源未释放或异常被吞掉 3. 是否引入了对全局状态的意外修改 4. 命名和代码风格是否与项目一致 5. 性能上是否存在明显问题 并输出一个结论:建议合并 / 打回修改 / 需要人工重点审查。实测下来,AI review 能筛出不少低级错误,比如 undefined 引用、错误处理缺失、不必要的外部依赖。但它也会有漏网之鱼,尤其是涉及业务含义和理解项目上下文的问题。所以我的定位是:AI 负责初筛,人负责裁决。AI review 说“通过”了,我还得抽查关键逻辑;AI review 说“有问题”,我基本都会信,因为这通常意味着代码确实有什么不对劲。
5. 常见问题与排查技巧实录
这部分直接整理成速查表,都是我和身边同事在 AI 协作模式下亲身撞过的问题:
| 现象 | 可能的根因 | 排查思路与解法 |
|---|---|---|
| 合并时整个文件被 AI 重写,冲突面积巨大 | AI 生成代码时做了大范围重构 | 用git diff --stat看文件改动规模;先git checkout --theirs引入对方版本,再手动把最小改动补进去 |
| JSON 配置合并后功能开关消失 | 文本合并工具吞掉了键 | 打开冲突文件,确认两边新增键都要保留;给 JSON 加 Schema 校验进 CI |
| Merge 到一半发现改不完了 | 冲突数量超出承受范围 | git merge --abort回退;把功能分支拆成更小的分支重新来 |
| 合完主干后测试大面积失败 | AI 引用了错误的 API 或假定的依赖不存在 | 全量编译 + 单测;重点搜索 AI 用到的陌生 API;对照官方文档验证 |
| rebase 过程中无限冲突 | AI commit 太大太杂 | 放弃 rebase,改用 squash merge;或git rebase --abort后用交互式 rebase 拆分 commit |
| 代码能跑但 Code Review 没人敢批 | 人类审查者无法理解 AI 意图 | 在 PR 描述里补充“AI 生成,已人工核验关键逻辑”的说明;要求 AI 生成附带设计意图注释 |
| 合并后出现重复的工具函数 | AI 没有感知到项目已有实现 | 全局搜索同名或功能相近的工具函数;合并阶段发现重复立即抽公共模块 |
再分享两个独家小技巧。第一个是“先看 blame,再决定谁改”。遇到合并后的诡异行为,先用git blame定位每行代码来源,如果发现某个函数全部来自 AI 生成的 merge commit,别犹豫,重写这个函数比你逐行理解它更划算。第二个是“给每个 AI 分支开一个专属临时分支”。让 AI 生成代码时不要把改动直接堆在你的工作分支上,而是让它在临时特性分支上干活,你审核通过后再用git cherry-pick把关键 commit 拿过来。这样即使 AI 写坏了,你的主分支永远是干净的,回退成本几乎为零。
6. 门槛降低不等于责任转移
折腾了这么久,我最深的体会是:AI 编程真正改变的不是“写代码”这个动作,而是我们对“合并”这件事的态度。从前我们写代码,是在用自己的逻辑组织系统,Merge 只是最后一步确认;现在我们写代码,是在替别人写的代码签字画押,Merge 变成了整个流程里最重要的一次决策。
我个人现在的状态是:代码生产速度提上来了,但我花费在需求澄清、设计约束、审查门禁上的时间反而更多了。我不再把“AI 一天交付多少行代码”当作绩效指标,而是看“这个月合入主干的代码有多少被回滚”。这个数字比任何时候都更能真实反映工程质量。
最后分享一个小建议:如果你也是个天天跟 AI 生成的代码打交道的人,多花点时间把团队的 CI 和 Code Review 流程打磨成“AI 友好型”——小步提交、语义化 commit、自动化检查全覆盖、JSON 有 Schema、冲突有预案。让 AI 去狂飙,让流程去兜底,让 Merge 重新变成一件有底气的事。毕竟,一个健康的仓库,从来不是看写入速度有多快,而是看每一次合并之后,系统还能不能长久地稳定运行下去。