多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

AI编程时代,为什么你越来越不敢点Merge?

AI编程时代,为什么你越来越不敢点Merge? 这个标题我盯着看了很久。因为我自己最近就处在这种状态里让AI写代码的时候越来越顺手但手指悬在Git面板那个绿色的Merge按钮上方时悬停的时间越来越长。以前写代码卡得最难受的是这段逻辑怎么实现现在卡得最难受的是这段AI帮我写的代码我到底要不要合进去。如果你最近也在用各类AI编程工具并且发现自己满足了一个奇怪的条件——提交代码的速度变快了合并代码的速度变慢了那这篇内容你应该能看进去。我会把这个不敢点Merge的状态拆开讲讲它到底是怎么发生的以及我后来是怎么调整心态、重建对Merge的信任的。1. 一个月前我以为自己解放了现在我知道麻烦才刚开始先说个真实场景。上个月我接手一个老项目的维护技术栈是Python FastAPI代码量不算大但历史包袱不小。我尝试着用AI辅助编程的方式去填一个比较复杂的权限校验模块两分钟不到AI给我生成了一整段包含装饰器、异常处理、日志记录的函数语法上挑不出毛病类型标注也写得像模像样我甚至觉得它比我写得规范。我几乎是无脑把它粘进了项目里加了个单测跑通了。那一刻的感受就是写代码的门槛真的归零了。但真正让我警觉的是第二天。同事A在另一个分支上改了同一个依赖模块的接口我们需要把两个分支合并。合并过程本身没有冲突Git直接自动合并通过了结果模块导入报错。我看了一眼发现AI生成的那个很规范的函数里面用的一个工具函数是旧版本API而同事A在合并前的分支里刚好把它改掉了。自动合并没有识别出这是同一个语义位置的修改因为位置不同语法上完全兼容但业务逻辑就断掉了。当时我就意识到写代码的门槛降低了但判断这段代码能不能进入项目的门槛反而升高了。以前我们写代码写的每一行都是有意为之的——哪怕这行写得不好至少我知道它为什么在这里。现在AI生成代码它是在预测一个最可能合理的答案它不知道我项目的隐式约定是什么不知道这段代码会在什么条件下被调用不知道我数据库里那几张表实际上有多少脏数据。这也是我现在对门槛归零这个说法的保留态度你不用会写代码就能得到一段代码但你依然需要会读代码、会验证代码、会给代码做好隔离和验收才能安全地让它进入主干。而且AI让得到代码这件事变得太便宜之后代码量会出现一种虚假的繁荣——提交的频率上去了PR的长度上去了但每一段代码的被验证程度反而是下降的。我当时反复问自己一个问题如果这段代码不是AI写的是一个外包工程师交给我的我会不会就这么合并答案是不会。我会问他要设计思路、要测试用例、要边界情况的说明。那为什么AI生成的代码我只因为它看起来能跑就准备合并呢这就是不敢点Merge的心理起点。1.1 先承认一个事实AI这块的能力确实在狂飙我不想让人误以为我在唱衰AI编程。说实话最近的进步是实打实的。以前我用AI写代码需要的提示词至少得带上一段上下文说明、函数签名、调用示例生成出来还经常要改好几轮。现在很多工具包括订阅制的Codex、各类IDE插件在日常CRUD、配置脚本、正则、业务逻辑封装这些场景下命中率已经高到可以直接进代码库了。像我这种后端为主、前端偶尔要碰的人以前写一个包含复杂状态管理的页面逻辑可能要折腾大半天现在AI能直接生成一个结构化程度不错的React组件Props定义、状态流转、事件绑定都给你排好。对于把一段思路变成代码这个环节AI确实把门槛碾平了。这也是为什么我会持续付费使用这些工具效率本身是真实的但效率不代表安全。我现在的态度是AI负责把翻译的工作做完理解我的描述变成代码我来负责验证的工作它写的东西是不是我真正想要的会不会在项目里埋雷。问题在于翻译变得太快而验证依然靠人这个速度差就是焦虑的来源。1.2 反直觉的现象PR变多了Merge变慢了我看了一下自己最近两周的工作记录提交到分支的commit数量比之前多了大概一倍但合并到主干前的代码评审时间也翻倍了。以前review一个同事的PR我主要看逻辑有没有问题有没有明显bug风格是否符合团队约定。现在review自己写的PR我得先重新读一遍AI生成的那部分确认它跟我当时想的一致。这中间最花费时间的一个动作叫做**代码行为还原**你看到一段AI生成的代码完成了一个特定功能但你看不出它为什么选择这种方式。比如一个简单的日期格式化AI可能给你写了一个看起来很严谨的parser但它在处理2026/01/05这种非标准输入时会出错而你项目里的数据恰好有一部分是这种格式。你如果把这段代码合进去运行期才会炸。这种不确定性会让Merge这个动作从一个日常确认变成一个重新验收。你得不断问自己这段代码覆盖了我说的场景了吗它有没有做它不该做的事它会不会影响我没有意识到的其他模块结果就是PR越来越多Merge越来越谨慎。这个状态其实不算坏事谨慎本身是工程质量的保护机制但如果谨慎的来源只是我不确定而不是我验证过那它带来的疲劳感和工作量是巨大的。2. 代码能跑和代码敢Merge信任断裂的具体时刻我一直觉得能跑和敢合是两回事。代码能跑说明它在某一个输入子集下表现符合预期敢合意味着你能为它在生产环境、边缘情况、未来演化中的表现负责。AI编程把前者做成了一件低成本的事后者却一点都没有变简单。信任断裂往往发生在几个非常具体的时刻我举几个自己真实经历过的2.1 代码生成器不知道上下文它只负责接话这是最核心的问题。AI代码生成本质上是一个语言模型的续写任务——它看到你前面的代码、你输入的提示词然后续写出最有可能的后续代码。它不知道的东西太多了不知道这个项目的编码规范里要求所有数据库操作必须走统一DAO层不知道团队约定禁止在业务代码里直接使用底层SDK不知道这个函数会被一个并发循环调用所以内部不能有共享状态。有一次我需要写一个缓存工具函数AI生成了一版用全局字典做缓存、带线程锁的实现逻辑上看是完备的。但那个项目是一个多进程部署的服务全局字典在进程之间根本不共享这个缓存函数等于没写。而且加上线程锁之后在单进程内还会带来无谓的锁开销。代码能跑单测能过但它在一个关键的前提下完全不成立。这个例子特别典型AI是在见到过的语料里找一个看起来合理的答案而不是在我们这个项目里找一个正确的答案。它的合理是统计学意义上的合理不是业务意义上的合理。所以它写出来的代码除非你有意把上下文喂得非常完整否则大概率会存在这种看不见的偏差。2.2 编译通过综合征跑通只是幻觉的起点我见过不少刚接触AI编程的开发者验证代码的方式就是跑通一下。程序不报错、接口返回200、页面能渲染出来就觉得OK了。但代码的可靠性从来不只是能跑而是覆盖率和边界行为的验证。AI生成的代码有个特点它在常规路径上的表现往往非常好在异常路径上经常偷懒。比如生成一个解析配置文件的函数正常情况处理得井井有条但如果你传进去一个空文件、一个带BOM头的文件、一个键值之间用了中文冒号的文件它可能会直接抛一个不相关的异常或者静默返回一个不完整的结果。这让我想到面试里经常问的空指针怎么处理——AI写代码同样存在主流程写得很爽边界条件一带而过的问题。你如果只是把它跑通那你验证的是AI在happy path上的表现而生产环境里更多时候是在跟边界条件搏斗。所以我现在养成一个习惯AI生成代码之后我会故意替换输入参数把正常的改成异常的把单数的改成空的把合法的改成非法的看它能不能体面地处理。只有当我大概知道这段代码在面对乱七八糟的输入时是什么表现我才敢把它变成别人的依赖。这一步没有捷径也没有AI能完全帮你代劳。2.3 AI会一本正经地制造看起来很合理的错误这是我个人最警惕的一点。AI生成的错误往往不是那种一眼就能看出来的低级错误而是脸上写着我很专业的错误。有一个印象很深的例子我让AI写一个批量导入数据的函数它生成了一段看起来非常优雅的代码——分批次处理、每批提交事务、出错回滚、还打了日志。但仔细一看它在所有行处理完后统一提交事务而不是每行独立提交导致如果第999行出错前面998行的结果片段性丢失。这个设计本身并不是错的但在我那个业务场景里导入任务允许好的保留、坏的跳过它这个回滚语义就会把所有好的全干掉。这种错误才是最麻烦的因为它在日常测试里表现完美你在做demo的时候根本不会触发第999行的异常。只有在生产环境的真实数据集上它才暴露出让你头疼的本来面目。我后来把这类错误总结成一类心智模型AI善于给出看起来完整、实则未经验证的设计方案。它像一个特别会写文档的架构师但他从没实际操作过你的业务。你如果把它的方案当成一个可以直接落地的成品那你就放弃了作为工程师最重要的判断权。这个判断权在Merge动作上体现得最充分——点击Merge等于说我作为工程师为这段代码背书。3. 代码审查变成信任审查Merge前我们到底在怕什么如果只是个人开发不敢Merge顶多是让自己多纠结一会儿影响还相对可控。但在团队协作里这个问题会被无限放大因为它已经不只是技术问题而是信任问题——你信任这段代码吗你信任生成这段代码的工具吗你信任自己具备审查它的能力吗3.1 以前的代码审查是看逻辑现在的审查是确认它不是垃圾传统意义上的代码审查重点看的是变量命名是否清晰、分支逻辑是否有遗漏、性能上是否有明显问题、有没有违反团队规范。这些都有一个比较明确的评判标准。AI时代代码审查开始变味——你经常要面对一个听起来有点荒谬但很现实的问题这段代码写得这么好它到底是真懂还是假装懂你会反复读一段AI生成的代码试图确认它是不是在一个错误的前提下构建了一个空中楼阁。可问题是你查不出明显的破绽因为它真的很规范该有的注释都有该有的类型标注都有。这时候你到底是Merge还是不Merge我的处理方式是把它写得是否规范的审查优先级放低把我是否知道它在干嘛的优先级放高。如果一段AI生成的代码我自己说不清楚它的执行路径那我宁可多花时间把它拆解开逐步验证也不让它糊弄过去。如果一段代码我看完了能跟同事解释它先做了A然后在B条件下跳转到C最后通过D返回结果那它至少是通过了大脑的验收。可以这么理解以前的审查是在代码集合上做模式匹配找异常现在的审查是在行为黑盒上做应激测试找不确定性。前者靠经验积累后者必须靠主动验证。主动验证的动作包括手动计算几个输入对应的输出、走查异常分支、观察它的日志输出是否和你预期一致。3.2 Merge动作本身的那点技术负担冲突、回退和不敢合有一件不得不提的事Merge这个动作本身在现代工程实践里已经变成了一个越来越重的操作。如果你在一个需求开发分支上工作了很久而主干发生了大量变动你面临着以下几个具体麻烦第一种麻烦是merge conflict。前阵子我就碰到过一次典型的Git冲突。我分支上改了一个JSON格式的配置文件中的A字段同事的主干上正好也改了同一个文件中的B字段。Git的文本合并算法能把两个改动都识别出来自动合并——语法上完全没冲突。但我们俩改的字段在业务上是有依赖关系的合并完之后配置里出现了一个逻辑矛盾程序加载时才发现问题。这个场景太常见了特别是在AI生成代码规模变大之后类似的假冲突越来越多。第二种麻烦是rebase还是merge的抉择。团队内部对此没有统一标准rebase会让历史变干净但重写历史在所有同事都已经拉取过分支的时候就麻烦merge历史有点乱但胜在保留真实发生过的合并事件。对一个同时有AI生成代码和人工修改的分支来说我现在的建议是优先保证可回溯性用merge少用rebase。因为AI生成的代码往往答案不同但表面相似一旦你rebase导致某次commit被改写后面排查这段代码是什么时候出来的为什么这么写就难很多了。保留原始提交记录至少还能追溯上下文。第三种麻烦是回退。不少人问过idea中如何回退merge操作这类问题其实就是对合并出错的恐惧。把那只看不见的手按在CtrlZ上才敢大胆合。但Git的回退和文本编辑器的回退完全是两回事——merge完成之后你要回退的是两个分支的交集而不是其中一个分支。你如果只是简单地把merge commit revert掉下次两个分支再合并时之前的改动会重新出现产生一次冤魂一样的冲突。基于这些细碎的恐惧Merge这个动作在心理上就变得很重不只是合代码而是承担最后一道责任。面对AI生成的海量代码这个责任的体量又被放大了好几倍。3.3 心理层面对别人的代码负责比对自己的代码负责更累还有一个被低估的心理因素那就是责任归属感。自己手写的代码出了bug你会觉得是我写错了我来修这种心态下你没有太多内心消耗修就行。AI生成的代码出了bug你会很自然地怀疑它到底哪来的这个写法我怎么会相信它这行是我漏看了还是它本来就不该存在这种半信半疑的状态导致的不是高绩效的谨慎而是折磨人的内耗。你说这个更像是编译器的自动补全还是更像一个不太靠谱的外包团队的代码我觉得更像后者——你没法在代码层面完全监控对方只能靠review和测试兜底。兜底的代价就是怀疑和焦虑。当AI产出一百行代码你的怀疑对象扩大到了一百行当它在数分钟内产出五百行你的怀疑对象扩大到了五百行而你只有一副大脑去审查。Merge之前的那种害怕本质上是颅内过载之后的自我保护反应。4. 重新建立对Merge的信心我用过的三层防御策略讲了这么多问题如果不给解决方案那这篇内容就变成了纯粹的焦虑贩卖。我自己踩了大概三周坑之后慢慢摸索出了一套相对稳定、能让自己的Merge恢复果断感的流程。我不说这是什么银弹但对多数以业务开发为主、借助AI写代码的团队来说可复制性比较强。4.1 第一层通过提示词给AI划定行为边界很多提示词教程教你要描述需求、描述场景、给例子这套东西我认可但我想提出一个额外强调的维度要在提示词里明确告诉AI什么不要做。举个例子。我让AI帮我写一个爬虫的HTML解析函数如果我的提示词是写一个解析HTML的函数提取页面里的标题和正文它大概率给你返回一个用正则或者BeautifulSoup实现的完整性不错的函数。但它可能也没管你项目中禁止使用request直连外网、必须走统一HTTP客户端的规定于是它给你的代码里直接import requests。代码没问题在你的项目里却是需要返工的。如果我稍微加一句约束注意本项目统一使用core.http_client模块发起所有HTTP请求不要直接使用requests库输出的函数应该是纯函数不依赖外部全局状态异常处理遵循项目中error_handling模块的约定那么它的生成结果就精准很多。此外我还会在提示词里要求函数足够小、职责足够单一。这个可能听起来反直觉但很重要AI生成一个大函数的能力其实不错但大函数的验证成本太高。你review一个三百行的函数和review三个一百行的函数心理负担完全不是一个量级。我宁可让它给我生成核心逻辑再让我自己把边界条件补上也不要它把整个模块一口气写完。4.2 第二层把测试前置——没有测试的AI代码不要合在这个问题上我要说一句非常实在的话AI写代码时代测试的价值被重新放大了。以前的测试是验证代码是否符合预期现在的测试是验证我对这段代码的理解是否成立。我现在的流程是每段AI生成的代码合进来之前必须带着三层测试单元测试覆盖常规路径和至少两个边界路径比如空值、超长字符串、非法枚举值。集成测试确保它跟真实依赖的服务能正确协作至少不能是启动时直接就崩。契约测试如果它要调用别的服务或者被别的服务调用我会检查它依赖的接口契约是否匹配真实接口。这三层听起来多但实际执行起来很多逻辑并不复杂。麻烦在于心态以前你可能觉得这段代码简单不用测了AI时代这句判断要反过来——越简单的AI代码越要测因为它可能是在简单感上精心设计过的反而隐藏了深层习惯。一个连测试都没有的AI函数合进去你的首要任务就是把它当作一个未归化的小野兽谁也不确定它对异常输入会做什么。提示如果你的项目目前根本没有测试基础设施需要先补一个最小测试框架再谈AI代码合并。没有测试兜底Merge按钮就没有心理重力你总会觉得随时会被线上事故打脸。4.3 第三层Merge前必须做一次五分钟自检清单除了自动化测试我还会给自己定一个手动检查的流程。每次准备Merge之前我会打开PR页面逐条过四问第一问这个PR改动的范围我是否每一个文件都看懂了吗如果看懂的标志是我能解释为什么这个改动要发生不是它改了啥。AI生成的代码里经常会出现顺手改掉无关逻辑的情况这个要盯得细。第二问这次改动的可回退性是不是明确的如果一个改动需要依赖数据库迁移、需要修改配置文件、需要重新生成缓存它的回退成本就很高。回退成本高的代码我倾向于拆分几次合并而不是一次性大爆炸。第三问我是否清楚这个PR deploy之后失败时的症状是什么很多人merge代码之前只想着应该能跑吧没想过如果跑挂了我能不能立即知道挂了、挂在哪一段。AI生成的代码尤其要把这个问题想清楚因为它的失败方式通常不太符合直觉。第四问我在合并之后是否有快速验证的手段你至少得知道上线后怎么确认这段代码在正常工作。如果是接口类代码我会提前准备一个curl命令或者写个临时脚本弹出来就跑一次如果是定时任务我会确认日志输出的关键字。这套五分钟自检看起来朴素但它能非常有效地把Merge之前的恐惧感转变成具体的、可控的检查动作。一旦你会把注意力集中在具体的检查上害怕就变成验证。5. 团队协作里那些没人写进文档的规矩如果你是一个人在写代码上述方案基本够用了。但绝大多数实际项目是团队协作Merge的勇气不仅来自个人技术还来自团队基础设施的支撑。这一章聊聊在AI编程普及的背景下我认为团队层面值得重新审视的规矩。5.1 Merge还是Rebase别吵了取决于你信不信历史先说这个永恒话题。真的没有放之四海而皆准的答案但我的倾向已经很明确AI编程时代我更推荐merge而不是rebase。原因前面提了一句这里展开说rebase的本质是把你分支上的每一次commit摘下来贴到新基线上。它重写了commit的哈希和时间线让历史看起来像是一条直路。好处是干净坏处是它抹掉了真实的并行开发痕迹。AI生成代码大量进入版本库后我发现一个很规律的现象一次业务改动里往往混着人工手写的commit和AI生成的commit。有时候AI一次性生成了一个不小的功能你会把这些代码拆成好几个commitcommit message写得很齐整。但过了一个月之后你想查这个功能当初为什么这么实现你翻commit记录看到的message可能写的是feat: add user import module然而这个commit本身可能是一段AI生成的代码它并没有写下完整的设计意图。Rebase会把这种commit拼成一条漂亮的直线好看但可回溯性很差。Merge虽然历史看起来错综复杂但它至少保存了一个真实的合并事件点——你还能顺着时间线找到在哪个时间点这两边的代码开始合流。对排查问题来说这种现实痕迹比美化后的直线有用得多。5.2 自动化检查要跟上代码质量和AI生成标记我在一些项目里试过一个做法在CI里增加了一个AI生成代码相关检查的环节。具体来说我会上传一个质量基线配置规则包括圈复杂度不能超过某个阈值AI生成代码有时会嵌套很深复杂度飙升这种代码review起来效率低风险高。重复代码率不能超过阈值AI特别喜欢复制粘贴既有模式重复代码率太高会让维护成本爆炸。禁止未定义的TODO和临时调试输出AI会在生成的代码里加一些看起来无害的TODO注释调试信息也出奇地多。这套检查当然不完美它更多是一个心理护栏提醒团队里的人AI代码不是自动化生成的免检品它要经过和手写代码一样的质量门槛。我还做过一个比较前卫的试验在PR描述里规定必须标注哪些代码是AI生成的、哪些是人工修改的。刚开始同事觉得有点怪后来发现这挺有用。因为review的时候人对AI生成的部分和人工修改的部分的关注点完全不一样——前者偏重验证行为正确性后者偏重逻辑设计合理性。5.3 JSON冲突和看不见的改坏格式冲突和语义冲突回到前面提到的JSON merge conflict问题这个问题值得单独说一下。JSON是很典型的文本合并能成功、语义合并会失败的格式。它不像代码有明确的函数边界它的键值对之间可能没有任何显式关系但业务逻辑对它们有隐式依赖。我见过一个真实的线上事故两个开发者分别对同一个配置文件增加了两个不同配置项且都准备在功能上线时开启。Git自动合并非常成功因为文本层面没有冲突。但配置项A的默认值依赖配置项B的存在配置项B在另一个功能分支上还没被合入。结果A功能提前上线读取配置时直接抛异常。这类问题的根因不是AI但AI编程会放大它因为AI生成代码时习惯性会附带配置文件修改它很可能在你没注意的地方自动加了你不需要的配置、或者改了某个默认值。所以在review AI生成的PR时我要额外拉一个文件清单出来看有没有哪些文件是AI顺手改的而不是本次需求必须改的。配置文件、依赖锁定文件、构建脚本这三个最容易出现顺手改动。5.4 让回退成为团队的底气前面提到的不敢Merge有一个很重要的底层原因大家不是真的不想合而是怕合了之后回不去。恐惧来自不可逆性。要缓解这个恐惧工程层面能做的事我把它们归纳为三件提交粒度要小一个分支尽量只做一件事commit之间保持逻辑原子性。任何AI生成的大块头代码先拆成逻辑上独立的区块每个区块对应一个commit。关键路径要有feature flag如果某个AI生成的重构或者新功能直接影响核心链路用灰度开关把影响范围先关起来。让Merge的即时风险从全局影响变成局部可控。保留清晰的发布标记每次上线之前打tag、写清楚发布说明。这样就算merge出问题你能快速知道上次正常的是哪个版本回退时不会被哪个版本好这个问题纠缠住。我特别推荐把小步提交作为一个团队纪律。理由很朴素小步提交时每次review的代码量小你对代码的理解度高测试也容易覆盖。当AI帮你生成一个完整模块时请把它改成分文件提交、分关注点提交而不是一个大commit导致review者看一眼都要累出工伤。6. 一点个人体会接受一半的信任写到最后我还是想聊点比技术更深一层的东西算是我这段时间踩完坑后的一点心态变化。我最早对AI编程的态度是来者不拒——它给我什么我吃什么反正我能验证能跑就上。后来变成草木皆兵——看到AI生成代码就条件反射式怀疑干什么都觉得心里没底。这两种都不对我现在的心态可以用一个词概括半个信任完整验证。半个信任的意思是我不再把AI生成代码看作一个需要全盘接受的成品而是把它看作一个能力不错但完全不了解我们项目的初级工程师。它的产出可能有七十分甚至九十分但阶段验收的责任永远在我身上。我会用测试去印证它用代码审查去审视它用五分钟自检去兜底它做完这些事情之后我真的敢点击Merge。同时我也想明白了一点不敢Merge并不是一件糟糕的事。它说明你的工程素养在提醒你——这段代码还没有通过完整的验收。它比那些无脑生成、无脑合并、线上翻车之后一脸茫然的状态要好太多了。所以如果你也处在这种写代码越来越快、点Merge越来越慢的阶段我认为你不用太焦虑。你只是正在建立一个新的、适应AI时代的工程本能。最后再分享一个小习惯吧。我现在每周五下午会固定做一次代码清理日把那些只在分支里跑过、还没合并的AI生成代码重新读一遍删掉明显不需要的简化掉绕弯路的补上缺失的边界测试。这个习惯帮我清理掉了很多留着也是留着的代码债务也让我周一的Merge动作轻松了很多。你要是也可以抽出固定的时间来做这件事点Merge时的那个犹豫感大概率会减轻不少。
返回列表