1. 先承认一个尴尬的事实:AI 写代码的产出翻倍了,我的理解速度却没跟上
AI 写代码很快,这一点我已经没什么好争论的了。从 Copilot 到 Cursor,一个上午能完成过去三天的工作量,PR 合入速度肉眼可见地提升。但真正让我慌的,是另一个问题:我越来越看不懂自己的系统了。
1.1 我是从哪一刻开始察觉的
变化不是突然发生的,它像温水煮青蛙。最早是同事在周会上问我某个订单模块的边界情况怎么处理,我下意识想翻代码,然后发现自己根本说不清楚那段逻辑到底为什么要这样设计。代码是我让 AI 写的,当时它给了我一个看起来完全合理、测试也全绿的实现,我review了一遍觉得没问题就合了。可三周后当真正的问题摆在面前,我发现自己在面对一个自己不熟悉的系统。
后来的迹象越来越明显:
- 一个原本两千行的模块,经过三轮 AI 辅助重构,膨胀到近六千行,但没有一个人能从头到尾讲清楚它的状态流转。
- 出 bug 时,团队的分工从"我知道该去哪一层改"变成了"先全库搜索关键词,再沿着调用链猜"。
- Code Review 的质量在下降。过去我会追问"为什么要引入这个抽象""这里为什么不用已有的工具类",现在 AI 给出的代码常常绕过了项目里已有的约定,而我因为没有参与生成过程,很难在评审现场快速判断它是否合理。
我做了个小实验:连续一周,刻意不依赖 AI 提示,纯手写一个中等复杂度的 CRUD 接口。结论很残酷——我写代码的手速变慢了,但写完之后我对这个模块的理解深度,明显高于同期 AI 生成并自行 review 的代码。慢不一定坏事,快也不一定占便宜,关键看你拿速度换走了什么。
1.2 "看得懂"到底是什么意思
很多团队在讨论 AI 写代码时,把"代码能跑"和"人看得懂"混为一谈。这两个根本不是一回事。我自己的定义里,"看懂一个系统"至少包含三层:
第一层,能说清行为。这个系统现在做什么,输入输出是什么,有哪些异常分支,这是最基础的。
第二层,能说清理由。为什么用状态机而不是 if-else,为什么这里要引入缓存,为什么这个字段必须加索引,为什么错误在这里吞掉而不上抛。理由层是普通文档和代码注释覆盖不到的地方。
第三层,能说清影响。如果我改了某个函数的内部实现,哪些调用方会受影响,哪些数据流会改变,系统的性能、可靠性、可观测性会不会变化。这是最高级的理解,也是架构师和资深工程师的核心能力。
AI 写代码的可怕之处在于,它极大地压缩了"行为层"的产出时间,却完全没有帮你积累"理由层"和"影响层"的认知。换句话说,你的系统里堆满了别人写的行为,而你的脑子里连理由都没有,更别说预测改动的影响了。
我记得很清楚,有一次我用 AI 生成了一段批量任务处理逻辑,它自动把任务分片、加锁、重试,甚至画了重试退避策略。当时我觉得真省心。结果过了一周,任务偶发卡死,我需要判断锁的释放条件、重试触发的边界,才发现自己完全不理解这段代码的调度意图。那一刻我明白了:AI 写代码的速度,是我把系统的"心智所有权"交给它的速度。
2. AI 生成的代码为什么天生"难懂":四个根因拆给你看
与其恐慌,不如拆解。AI 生成的代码难懂,不是玄学,背后有几个非常具体的机制。理解了这些机制,你才知道怎么防。
2.1 它只回应 Prompt,不回应系统的完整上下文
人类程序员写代码时,脑子里装着大量上下文:上次评审时大家约定不能用全局静态变量、这个模块的异常统一走某个包装方法、数据库连接必须由外层管理、某个接口虽然现在没人调用但后续要兼容第三方。这些约束不在任何一份文档里,它们分布在团队的对话、代码历史和个人经验中。
AI 没有这些。你给它一个 prompt,它生成的是一个"孤立的正确"——从局部描述看完全合理,但它不知道项目里已经有一个同功能的工具类,不知道团队约定的命名风格,更不知道某个看似多余的防御性判断其实是历史事故的产物。
我举个实际例子。有次我让 AI 帮忙生成一个 Excel 导出的工具方法,它很贴心地自动创建了临时目录、加了文件清理逻辑。功能确实能用,但项目里明明已经有统一的上传组件,而且那两个临时目录的清理逻辑和现成的调度任务冲突,导致半夜的清理任务把别的模块刚生成的文件删了。这个 bug 花了我们整整一天才查出来。AI 没有错,它只是不知道系统的上下文。
2.2 它生成的是"统计平均",不是"团队约定"
大模型学的是海量代码的统计规律,所以它产出的代码风格接近"互联网平均水平"。而一个健康的项目,代码风格往往是一系列"非平均"的刻意选择:别的项目习惯返回 null,你们约定必须返回 Optional;别的项目异常往上抛,你们的网关要求返回错误码;别的项目日志用英文,你们的日志规范要求中文可读且包含 traceId。
AI 生成的代码天然偏向统计平均,这就意味着它产出的代码和团队的既有约定之间,永远存在持续的摩擦。小到命名,中到异常处理粒度,大到分层方式,每次你都得花额外的心力去修正。更麻烦的是,当 AI 在现有代码基础上做增量时,它会把前人的"非平均约定"也当成一种模式去放大,抽象风格越来越偏,最终变成谁都不愿意碰的代码。
还有一个特征值得注意:AI 既过度抽象,也过度复制。同一个校验逻辑,它可能在一处替你封装成工具函数,在另一处又原地重写一遍。这种不一致性比单纯的多或简洁更要命,因为它让读代码的人无法建立稳定的预期——你不知道进入这个代码库后,会遇到什么风格的编排。
2.3 它输出"是什么",但完整丢弃了"为什么"
人类写代码的时候,意图是渗透在每一个角落的。函数命名里带着动机,注释里写的是来龙去脉,commit message 里有上下文,有时候连某个魔数都是历史约定的延续。AI 生成代码时,这些信息基本归零。它给你的是纯粹"是什么"的骨架,不带任何目的。
这个问题的杀伤力在维护阶段才会显现。你拿到一段 AI 生成的状态机配置,看到 A 状态可以转到 B 和 C,但你不知道为什么要允许 C,不知道这个分支是正面的业务规则还是历史妥协。你不知道,就不敢动;不敢动,系统就开始冻结——新需求来了你宁可新包一层,也不敢改那层没人理解的逻辑。技术债务就是这么滚起来的。
我自己有个习惯,AI 生成的代码我会刻意要求它补一段"这段代码解决什么问题、为什么这么设计"的说明,然后删掉代码里的冗长注释,把这段意图留在 commit message 里。后来发现,这比留注释还有用——至少三个月后翻 Git 历史的人,能看到当时为什么做成这样。
2.4 高速迭代放大了路径依赖,让早期偏差滚成雪球
路径依赖是最后一块拼图,也是最隐蔽的。AI 写代码不是从白纸开始,它会敏锐地观察你当前代码库的模式,然后模仿。如果这个模式是对的,那是良性循环;如果这个模式本身就有问题,AI 会以惊人的效率把这个错误模式复制到整个系统。
我见过一个典型的例子:某个服务早期图省事,直接在 Controller 里写业务逻辑。后来团队用 AI 做新需求,因为 AI 看到大量 controller 里塞业务逻辑的"先例",于是每个新接口都照着这个模式写。三个迭代下来,业务逻辑全堆在 Controller 层,Service 层形同虚设。你很难怪 AI,因为从统计上看,这就是这个代码库的"约定"。
路径依赖最可怕的地方在于它积少成多。每一次单看都只偏离一点点,但合在一起,系统的架构边界、依赖方向、错误处理方式,都会慢慢偏离团队原本的设计意图。当这种偏离积累到一定程度,系统就不再是"你设计过的那个系统"了,而是一个"长成了那样"的系统。
3. 我亲手踩过的三个坑:不是 AI 不好用,是我把理解权让渡了
讲了这么多机制,还是落到自己的经历上。这三个坑都不算惊天动地,但它们真实地改变了我的工作习惯。
3.1 幽灵状态机:AI 补全的迁移逻辑漏了三个边界
去年做一个订单流转模块,原来的实现是一串 if-else 判断状态能不能切换,代码很丑。我想着让 AI 帮忙改成状态机,顺便自动补全状态迁移表。AI 很快给出了一个很漂亮的实现,迁移矩阵清晰,单元测试也写好了,全是绿的。我 review 的时候觉得逻辑完备,就合入了。
结果上线两周后,出现一种罕见的订单卡死:订单在"已支付"状态下被用户申请售后,售后完成后需要通过一个特殊路径退回"待发货"。这条路径在老的 if-else 里有,但 AI 根据我们给的业务描述自动补迁移表时,漏掉了这个只在售后场景才出现的分支。
排查花了整整三个下午。最痛苦的不是查 bug 本身,而是我们所有人都需要先花时间理解这套 AI 生成的状态机——它的迁移表是怎么组织的,补全逻辑基于什么假设,然后才能谈得上去查漏网分支。如果这段状态机是人写的,迁移表旁边会有一行注释说明"售后闭环需要绕过常规状态路径",我们至少能少猜两个小时。
那次之后我定了一个规矩:凡是 AI 生成的状态迁移、权限矩阵、配置映射这类"表格型逻辑",必须手检每一个迁移对,并且强制自己画出完整的业务路径图,不允许直接拿 AI 的结果当最终版。
3.2 更优雅也更危险的"优化":重构核心支付链路
另一个项目,支付回调的代码里有一个巨大的 switch-case,根据支付渠道分发处理逻辑。AI 提示我可以用策略模式重构,代码会更清晰。当时的我上头了,让 AI 直接重构。
重构后的类结构确实漂亮:一个策略接口、七八个实现类、一个工厂根据渠道类型返回对应策略。单元测试跑完,全绿。我觉得自己完成了一次漂亮的现代化改造。
问题出在重构过程中,AI 为了"统一处理逻辑",把原来每个渠道各自的缓存失效逻辑合并成了一个通用的缓存策略。结果某个渠道的回调数据更新后,缓存没有按原来逻辑失效,导致用户看到支付状态迟迟不更新。这个 bug 非常隐蔽,因为单测覆盖的是"策略选择"和"处理主流程",根本不覆盖渠道特有的缓存行为——那部分逻辑原来散在 case 分支里,重构时被当成重复代码干掉了。
这里我得到的教训不是"不能重构",而是"重构是理解系统的最好时机"——你在重写的同时,必须把每个分支的寄生逻辑逐条摊开,搞清楚它为什么存在。这个工作交给 AI,等于主动放弃了对存量系统的一次深度盘整机会。现在我的铁律是:AI 可以帮我起草重构方案,但每个渠道分支的原始行为清单必须我亲自梳理,AI 输出的常量映射和旧行为对照表我要逐项勾对。
3.3 五分钟写完的数据脚本,靠了半个月补救
第三个坑甚至不在主干代码里,而是一个一次性数据迁移脚本。当时要把旧库的用户等级映射到新库的等级体系,我让 AI 生成了一个 Python 脚本,肉眼看了下逻辑觉得没问题就跑完了。跑完一看,迁移总数对得上,好,收工。
过了半个月,数据分析那边报告新库的等级分布和预期偏差很大。一查才发现,AI 写的映射逻辑把所有"未知等级"都默认映射成了最低等级,而这些"未知"里有一部分其实是旧的渠道专属等级,需要分渠道映射。脚本是全量跑的,脏数据已经铺进去了,最后花了半个月写修正脚本、补对账流程、和业务方一个个解释。
这类脚本往往不经过 code review,也不进测试体系——它"看起来跑得很快,结果也对得上",于是所有人都默认它是对的。这个坑教会我两件事:第一,一次性脚本本质上也是系统的一部分,它的假设条件(哪些值可以忽略、哪些分支视为默认)必须写清楚,并且跑之前要人工核对输入分布;第二,AI 生成的处理逻辑里默认值选择往往过于"顺滑",它倾向于给出一个看似合理的兜底,而兜底恰恰是最需要人去确认的地方。
4. 把"代码操作员"拉回"系统负责人":五个能落地的做法
分析了问题,也讲了我自己的教训,接下来是解决方案。这五个做法不是理论,是我试下来真的有帮助的,按投入成本从低到高排序。
4.1 把"让 AI 解释清楚"变成每一段代码的必经步骤
最简单、最立竿见影的一条:AI 生成代码后,不要直接 review 完毕就完事。无论多忙,逼自己在心里把这段代码的执行过程"脑内跑一遍",然后合上编辑器,用自己的话复述一遍这段代码做什么、为什么这么做、它会怎么失败。能讲清楚,才算真正过手了。
这个技巧叫 Feynman 学习法,放在 AI 生成代码的场景特别合适。因为 AI 的产出很容易让你产生"我已经理解了"的错觉——代码看着合理,测试也绿了,你很容易就把"合理"当成"理解"。但只要你尝试不借助编辑器复述一遍,立刻会暴露自己是不是真的掌握了这个设计。
我还习惯做一件事:选关键的函数,反向要求 AI 生成"设计说明",内容必须包括前置条件、后置条件、失败模式、为什么没有采用替代方案。然后我把这段说明压缩成一个 commit message 写进提交里。以后任何人翻 Git 历史,都能靠这个信息快速重建上下文。这个操作每次多花三到五分钟,但省下来的排查时间远超投入。
4.2 静态读不懂的时候,切换到运行时观测的视角
如果系统已经膨胀到静态阅读完全追不上,就别硬啃代码了。换个思路:看运行时。借助 APM、分布式追踪、监控面板和日志系统,观察系统在实际生产环境中究竟跑了哪些路径、哪条链路慢、哪个分支分支频繁被触发。运行时数据比静态代码更能准确地告诉你系统"实际上是什么样"。
我自己的做法是,对新接手或者久未维护的模块,先不让 AI 直接改代码,而是先搭一套观测视图:关键接口的调用次数、耗时分布、异常类型、依赖方清单、上下行链路。用这些数据画出系统的"运行时地图",然后再回到代码里,只读地图上那些真正热门的路径。你会发现,其实日常 80% 的事务都集中在少数几条核心链路上,先把这几条链路吃透,系统的"看不懂焦虑"就能缓解一大半。
运行时观测还有一个额外好处:它能发现代码里"看着重要但根本没人调用"的死代码和"看着不重要但实际上在关键路径上"的暗雷。这种认知偏差,纯靠读代码很难察觉,而 AI 生成的代码尤其容易制造这种假象——因为它的结构看起来总是那么完整、那么合理。
4.3 用自动化的架构守护工具,卡住系统的边界漂移
AI 写代码最大的风险之一,是它不遵守架构边界。依赖方向错了、底层模块调了上层模块、基础设施代码出现在业务领域里,这些在 AI 生成代码时极易出现。人工 review 去盯这种问题很累,而且人的注意力有限,盯得住主线就盯不住支线。正确的做法是把边界规则自动化,让 AI 生成的代码直接撞墙。
具体来说,我推荐用 ArchUnit(Java 生态)、dependency-cruiser(JavaScript/TypeScript 生态)这类静态架构测试工具,把依赖规则写成可执行的测试:
- 禁止 controller 层直接访问 repository 层;
- 禁止 infrastructure 包反向依赖业务领域包;
- 禁止 util 包依赖业务模块。
- 所有跨模块引用必须显式通过接口声明。
配上 CI 之后,AI 生成的代码如果违反边界,直接红灯,根本走不到 review 环节。这个动作的意义不在于抓 bug,而在于维持系统的"可理解性"——只要边界稳定,你在读任何一段代码时都能靠模块位置推断它的角色,这种可预期性是理解系统的基础。
4.4 契约优先:先定行为,再让 AI 填实现
这是一条被低估的准则。很多人用 AI 开发是"先让它写实现,再补测试",结果测试变成了 AI 自证清白的工具——实现和测试同源,自然全绿。正确的顺序应该反过来:先把接口轮廓、输入输出约束、异常行为、边界条件定义成契约,用测试把契约固化下来,然后让 AI 去填实现。
消费者驱动的契约测试也很有价值:每个下游服务对上游接口的期望,以契约的形式独立维护;上游改动时跑契约测试,过了才能合并。这样一来,系统的行为有了锚点,AI 生成的实现如果符合契约,说明它没有偏离语义;如果不符合,无论代码多漂亮都是错的。这套机制直接缓解了"AI 代码不可信"的根本焦虑——我们不用信任实现,我们只信任契约,而契约是人为定义、可审查的。
我实践下来的体感是,契约优先的开发流会让 AI 的产出效率打一点折扣,因为前期定义契约本身要花时间。但从两周的维度看,总账绝对划算:契约先定,AI 的实现基本一次过 review,后续改动也有回归保障,系统性翻车概率大幅降低。
4.5 给 PR 模板加一道"认知税",拒绝看不懂的合并
最后一条实操性最强,也最容易在团队里落地。改造 PR 模板,要求每个 PR 必须回答几个问题:
- 这个改动解决什么问题,为什么采用当前方案?
- 影响范围是什么?哪些模块、哪些调用方会受影响?
- 这个改动依赖什么前置条件,引入什么副作用?
- 如果系统行为发生变化,变化点在哪里?
这几个问题的目的,是逼迫提交者(包括用 AI 完成工作的自己)把"为什么"写下来。写不出来的 PR,说明 AI 产出之后迭代者也没有真正理解,那就没有资格合入主干。
很多团队担心这样的模板太重,会拖慢 AI 的效率优势。我的体会是:要的就是这个"慢"。AI 帮我们省下了写代码的时间,这部分时间就应该花在理解代码上。如果 AI 省下来的时间没有转化为更高密度的思考,而是变成了更多、更快的代码堆叠,那系统崩溃的速度只会更快。
5. 给依赖 AI 上头的团队:三条军规和一套体检方案
方法论说完,最后说几条偏"军规"性质的底线。这些不是软性建议,是我觉得所有认真用 AI 写代码的团队都应该立起来的规矩。
5.1 军规一:AI 只能做实现建议,不能做架构决策
架构决策是取舍,取舍意味着在多个可行方案中选择最适合当前系统的那个,这个选择依赖的是对现有系统的深度理解、对业务未来演进的判断、对团队能力的认知。这些 AI 都不具备,它只能告诉你"通常怎么做更好",而"通常"恰恰是我们在自己的系统里需要警惕的东西。
我现在的做法是:AI 给出方案建议后,我强制自己写一段"为什么选择这个方案、放弃什么、代价是什么"的评审意见,再让 AI 去实现细节。架构评审必须有人兜底,不能把架构级的问题丢给 prompt。
5.2 军规二:越核心的代码,越要人肉写、人肉读
我给自己划了一条线:支付、鉴权、库存扣减、数据一致性相关的核心逻辑,AI 可以辅助生成雏形、可以帮忙生成测试数据、可以帮忙做代码审查建议,但最终实现必须由人类逐行编写并逐行理解。不是 AI 写不好,而是这些代码的失败成本太高,而理解权是唯一的抗风险资产。你把理解权让渡给 AI,等于在核心链路上蒙眼狂奔。
这跟坐飞机一个道理:自动驾驶可以辅助飞行员,但起降阶段飞行员必须手动操作,因为起降阶段的偏差代价是不可接受的。核心代码就是我们的起降阶段。
5.3 军规三:代码库的"可理解性"要像测试覆盖率一样被追踪
团队里常讨论测试覆盖率、缺陷率、变更成功率,很少有人讨论"可理解性"。但一个没人理解的重要模块,和一个有明确 bug 的模块,风险是等同的——前者甚至更危险,因为你不知道它会在什么条件下出错。
我的建议是把可理解性量化:每季度抽 30 分钟做一次"代码考古",随机找一个核心模块,不看任何文档、不看 Git 记录,只靠代码回答三个问题——这个模块做什么、为什么这样做、改了会有什么影响。答不上来的人,去学习并把这个模块的认知写成笔记;答上来的,把学习笔记沉淀成团队知识库。这个活动成本不高,但它逼着团队保持对系统的持续理解,防止 AI 的效率优势把所有人拖进"只管生成、不管理解"的深渊。
5.4 定期给系统做"体检",而不是等问题暴露
最后分享一个我正在使用的循环节奏:每两个迭代安排一次"系统理解体检"。体检不是代码 review,而是走一遍完整的认知流程:从需求入口进入,沿调用链走到底层存储,沿途记录每一步的"理解状态"——这里我能解释为什么,这里我只能解释是什么,这里我完全不懂。
这个体检产出的不是 bug 列表,而是一份"理解赤字清单"。下一轮迭代里,团队优先补最影响未来改动的赤字。我自己的体感是,这种基于理解赤字的排期方式,比基于代码坏味道的排期更贴近真实的系统风险。因为有些代码虽然丑,但团队对它很熟;有些代码非常"干净",却无人理解——后者的风险远远大于前者。
写到这里,再回头看"AI 写代码很快,我却越来越看不懂系统"这个题目,我的答案其实已经清楚了:问题从来不在 AI,而在于我把"理解系统"这件核心职责悄悄外包了出去。AI 是一个效率放大器,一面前所未有地加快了代码产出,一面对着系统里的认知负债按了快进键。
我现在的态度是:AI 用来写代码依然很爽,但我为每段 AI 代码额外支付一笔"理解税"——写设计说明、脑内仿真实行路径、梳理影响范围、做运行时验证。这笔税看起来很麻烦,但它买到的不是代码,而是我对系统的掌控感。没有这种掌控感,代码生成得再快,也只是在加速制造我不认识的东西。