1. 当代码产出速度超过理解速度,问题才真正开始
最近半年,我身边不少做开发的朋友都陷入了一种很微妙的状态:代码提交记录越来越密集,功能上线越来越快,但打开项目仓库的那一刻,心里反而越来越没底。AI 写代码确实快,快到什么程度?以前一个中等复杂度的业务模块,从设计到联调可能要两三天,现在把需求描述清楚,几分钟就能拿到一份能跑通的初版。可问题是,这份代码跑通之后,很多人(包括我自己)会盯着屏幕愣住——这玩意儿到底是怎么工作的?为什么这里要加锁?那个状态机为什么绕了一圈?如果线上出了边界问题,我该从哪里下手?
这不是个别现象。我在几个技术群里做过小范围交流,大家的反馈高度一致:AI 编程工具把“写”的门槛降到了历史最低,却把“读”和“改”的门槛悄悄抬高了。以前自己一行行敲出来的代码,哪怕写得烂,至少脑子里有完整的执行路径;现在 AI 生成的代码结构往往更“标准”、更“工程化”,但正因为它的组织方式和你原本的思维习惯不一致,理解成本反而飙升。
这篇文章不打算讨论“AI 会不会取代程序员”这种老生常谈的话题,也不准备推荐某个具体的 AI 编程插件。我想聊的是更实际的问题:当 AI 成为日常编码的一部分,我们怎么避免自己从“开发者”退化成“代码搬运工”。具体会涉及几个方面:为什么 AI 生成的代码特别容易让人“看不懂”、怎么建立一套有效的代码审查和消化流程、哪些场景适合让 AI 放手写、哪些场景必须自己掌控核心逻辑,以及我在这半年里踩过的坑和总结出的实操方法。
如果你也在用 AI 辅助写代码,并且隐约感觉到“速度上去了,掌控感下来了”,那这篇内容应该能给你一些参考。不管你是刚接触 AI 编程的新手,还是已经用了几个月的熟手,下面的经验都尽量做到可以直接拿去用。
2. AI 生成代码的“黑箱感”到底从哪来
2.1 不是代码质量差,而是思维路径不匹配
很多人把“看不懂 AI 代码”归咎于代码质量不行,其实这个判断不太准确。我对比过自己手写和 AI 生成的同类模块,客观说,AI 在命名规范、异常处理、边界检查这些“表面功夫”上往往做得比我好。真正的问题在于思维路径的差异。
你写代码的时候,脑子里有一条清晰的因果链:因为业务要求 A,所以先做 B,再做 C,最后用 D 兜底。这条链子是你自己推出来的,每一步都有“为什么”撑着。AI 生成代码时,它的“推理”过程对你来说是隐式的——它可能基于训练数据里大量类似场景的模式,直接给出了一个“统计上最优”的结构。这个结构本身可能是合理的,但它跳过了你习惯的那些中间步骤。
举个很简单的例子。我让 AI 写一个“根据用户等级计算折扣”的函数,它返回的代码里用了一个策略模式,把不同等级映射到不同的折扣计算类。从设计模式角度看,这很优雅;但我原本的预期只是一个 if-else 链。结果就是,我要花额外的时间去理解它为什么引入策略模式、这些类之间的调用关系是什么、以后加新等级该怎么扩展。代码本身没问题,问题是我需要“逆向工程”它的设计意图。
2.2 上下文缺失导致“局部合理、全局突兀”
AI 生成代码的另一个典型特征是:单看某个函数或类,逻辑自洽;放进整个项目里,却显得格格不入。原因很简单——AI 在生成那一刻,看到的上下文是有限的。它可能知道你当前文件的代码,甚至能读取几个相关文件,但它很难完整理解你整个项目的架构约定、历史包袱和隐性规则。
我遇到过好几次这样的情况:AI 生成的工具类里,日期格式化用了某个第三方库,而项目里其他地方统一用的是另一个库;或者它生成的异常处理直接抛了运行时异常,而项目的全局异常处理器期望的是自定义业务异常。这些细节在 AI 的“视野”里可能根本不重要,但对维护者来说,就是一颗颗需要手动拆除的雷。
注意:AI 编程工具通常会在生成时参考当前打开的文件和少量上下文,但它不会主动去理解你项目的“潜规则”。这些潜规则包括:日志格式、异常体系、依赖注入方式、配置读取路径、测试命名规范等等。你不主动告诉它,它就会按自己的“默认习惯”来。
2.3 快速迭代掩盖了理解债务的累积
最危险的地方在于,AI 让“先跑起来再说”变得太容易了。以前你遇到一个不太懂的模块,可能会花半小时读代码、画流程图,因为改动的成本高,你不得不理解。现在呢?AI 几秒钟就能给你一个新版本,你很容易养成“不理解就重新生成”的习惯。
这个习惯短期看效率极高,长期看却在积累理解债务。每一段你没真正消化的 AI 代码,都像一笔未还的贷款。当系统规模还小、功能还简单的时候,利息很低,你感觉不到压力。一旦系统变复杂、需要跨模块改动、或者线上出了诡异 bug,这些债务就会集中爆发——你会发现自己在面对一个“别人写的”系统,而这个“别人”其实是一堆你从未真正理解的生成结果。
我自己的教训是:有一个后台管理模块,前后让 AI 生成了大概七八个版本,每次都是“能跑就行”。直到有一天需要加一个权限校验逻辑,我打开代码发现,里面有三套不同的权限判断方式,互相之间还有冲突。那一刻我才意识到,我一直在“用”这个模块,但从来没有“拥有”过它。
3. 建立“消化式审查”流程,而不是“扫一眼就合并”
3.1 把 AI 生成代码当成新人提交的 PR
我现在处理 AI 生成代码的方式,和 review 一个刚入职同事的 PR 几乎一样。核心原则是:不理解的代码,不合并。听起来很基础,但实际操作中,很多人(包括以前的我)会因为“这是 AI 生成的,应该没问题”而放松警惕。
具体怎么做?我会把 AI 生成的代码先放到一个独立分支,然后逐段过一遍。遇到任何“为什么这里要这样写”的疑问,要么让 AI 解释,要么自己查文档,要么直接改成我能理解的形式。这个过程确实比“生成完直接提交”慢,但它把理解成本前置了,避免了后期更大的维护成本。
有一个很实用的技巧:让 AI 自己解释它生成的代码。你可以直接问它“这个函数里为什么用了递归而不是循环”“这个状态变量的生命周期是怎样的”“如果输入为空,执行路径是什么”。AI 的解释不一定完全准确,但它能帮你快速定位到需要重点理解的部分。
3.2 用“三遍法”快速消化陌生代码
对于稍微复杂一点的 AI 生成模块,我总结了一个“三遍法”,实测下来能在半小时内建立起基本的掌控感。
第一遍,只看输入输出和副作用。不纠结内部实现,先搞清楚这个函数/类接收什么参数、返回什么结果、有没有修改外部状态(比如写数据库、发消息、改全局变量)。这一步的目的是画出模块的“边界”。
第二遍,追踪主流程。从入口开始,沿着最正常的执行路径走一遍,忽略异常分支和边界条件。把主流程用几句话写下来,比如“先校验参数,然后查缓存,缓存没有就查数据库,最后写回缓存并返回”。这一步的目的是建立“骨架”。
第三遍,专门看异常和边界。空值怎么处理?超时怎么办?并发冲突怎么解决?这些往往是 AI 生成代码里最容易埋雷的地方,也是线上问题的高发区。把每个异常分支的处理方式记下来,和你的预期做对比。
三遍走完,你对这段代码的理解基本就够用了。如果还有不清楚的地方,那就是需要重点标注、后续深入的地方。
3.3 给 AI 代码打“理解标签”,而不是“信任标签”
我见过一些团队的做法是:只要是 AI 生成的代码,就在提交信息里标注一下。这个习惯本身没问题,但标签的语义很重要。如果标的是“AI 生成,已审核”,那审核的人可能只是扫了一眼;如果标的是“AI 生成,主流程已理解,异常分支待验证”,那后续维护的人就知道该重点关注哪里。
我自己的做法是在代码注释里加一个简单的标记,比如:
# [AI-GEN] 主流程已消化,缓存穿透处理逻辑待补充测试 def get_user_profile(user_id): ...这个标记不是给别人看的,是给三个月后的自己看的。三个月后你打开这个文件,看到这个注释,就知道当时自己的理解边界在哪里,不会盲目地认为“这段代码我肯定懂”。
提示:理解标签不需要很正式,关键是记录“当时理解到什么程度”和“哪里还不确定”。这比笼统的“已审核”有用得多。
4. 哪些场景可以让 AI 放手写,哪些必须自己掌控
4.1 适合 AI 主导的代码类型
经过这半年的实践,我大致摸清了 AI 编程的“舒适区”。以下几类代码,我基本会让 AI 主导生成,自己只做最终审查:
第一类是样板代码和重复性结构。比如 CRUD 接口、DTO 转换、配置文件解析、简单的工具函数。这些代码逻辑直白、模式固定,AI 生成的版本往往比手写更规范,而且不容易出错。
第二类是测试代码。让 AI 根据现有函数生成单元测试,效率极高。它会自动覆盖很多你没想到的边界情况,比如空输入、超长字符串、特殊字符等。当然,测试断言是否合理需要你自己判断,但生成测试用例这个环节,AI 确实省时间。
第三类是文档和注释。把一段代码丢给 AI,让它生成函数说明、参数解释、使用示例,质量通常不错。尤其是那些你写的时候就没想好怎么命名的变量,AI 反而能给出更清晰的描述。
第四类是探索性代码。比如你想快速验证一个算法思路、试一个第三方库的用法、写一个临时脚本处理数据。这种代码生命周期短、影响范围小,让 AI 快速产出原型,不合适就扔掉,成本很低。
4.2 必须自己掌控的核心逻辑
但有几类代码,我坚持自己写,或者至少自己先写出骨架再让 AI 填充细节:
第一类是核心业务规则。比如订单状态流转、资金计算、权限判定、风控策略。这些逻辑直接关系到业务正确性和资金安全,必须由人来定义每一步的意图和边界。AI 可以帮你写实现,但“什么情况下应该拒绝”“什么情况下应该人工介入”这些决策,不能交给 AI。
第二类是并发和状态管理。多线程、分布式锁、事务边界、缓存一致性——这些地方的 bug 往往极其隐蔽,而且复现困难。AI 生成的并发代码可能在语法上没问题,但在内存可见性、锁粒度、死锁预防这些方面,需要非常有经验的判断。我自己的做法是:并发相关的核心代码自己写,AI 只用来做辅助审查或生成测试。
第三类是安全相关代码。认证、授权、加密、输入校验、SQL 拼接——这些地方一旦出错,后果可能是灾难性的。AI 可能会生成“看起来安全”的代码,但它对具体的安全威胁模型理解有限。安全代码必须由了解系统威胁面的人来写。
第四类是架构决策相关的代码。比如模块划分、接口定义、依赖方向、数据流设计。这些决策影响的是整个系统的可维护性和扩展性,AI 可以给你建议,但最终拍板必须是人。我通常会让 AI 生成几个备选方案,然后自己分析利弊,选定一个后再让它辅助实现。
4.3 一个简单的判断标准
如果你不确定某段代码该不该让 AI 主导,可以用一个简单的问题来判断:这段代码出错时,我能不能在五分钟内定位到原因并修复?
如果能,那让 AI 写没问题,因为试错成本低。如果不能,比如涉及复杂的调用链、隐蔽的状态依赖、或者线上故障影响面很大,那就自己掌控核心部分。这个标准不完美,但足够实用。
5. 把 AI 从“代写”变成“结对”,沟通方式很关键
5.1 提示词里要包含“约束”而不只是“需求”
很多人用 AI 写代码时,提示词只写“我要一个什么功能”。这样 AI 会给你一个“通用最优解”,但这个解可能和你的项目格格不入。更好的做法是:在提示词里明确约束条件。
比如,不要只说“写一个用户注册函数”,而是说“写一个用户注册函数,要求:使用项目现有的 UserRepository 接口、异常统一抛 BusinessException、密码加密用 BCrypt、注册成功后发送欢迎邮件但邮件失败不影响注册结果、日志用 slf4j 的 info 级别”。约束越具体,AI 生成的代码越贴近你的实际需求,你后续的理解成本也越低。
我通常会在一段提示词里包含这几类约束:依赖约束(用哪些现有类/接口)、异常约束(错误怎么处理)、日志约束(记录什么、什么级别)、测试约束(需要覆盖哪些场景)。这些信息不需要很正式,用自然语言列出来就行。
5.2 让 AI 先写“设计说明”再写代码
对于稍微复杂一点的功能,我现在会分两步走。第一步,让 AI 用自然语言描述它打算怎么实现,包括:会创建哪些类、每个类的职责是什么、核心流程分几步、异常情况怎么处理。第二步,我审查这个设计说明,确认没问题后,再让它生成代码。
这个做法有两个好处。一是设计说明比代码好读,我可以在几分钟内判断它的思路对不对,避免生成一大堆代码后再发现方向错了。二是设计说明本身就是理解代码的钥匙,后面看代码时,对照着设计说明,理解速度会快很多。
如果 AI 的设计说明里有我不认同的地方,我会直接指出,让它修改设计,而不是等代码生成后再去改代码。改设计的成本远低于改代码。
5.3 要求 AI 标注“不确定”的地方
AI 有一个特点:它很少主动说“我不确定”。即使某个地方它只是猜测,也会用很自信的语气写出来。这对理解代码很不利,因为你不知道哪些部分是可靠的,哪些部分需要重点验证。
我的应对方法是:在提示词里明确要求它标注不确定的地方。比如加一句“如果你对某个实现方式不确定,或者有多种可选方案,请在代码注释里说明”。实测下来,AI 确实会标注出一些“这里假设了 X”“这个方案在 Y 情况下可能有问题”之类的注释。这些标注就是你需要重点审查的地方。
另外,对于 AI 生成的代码,我会特别关注它引入的新依赖、新工具类、新配置项。这些往往是“不确定”的高发区,也是和项目现有体系冲突最多的地方。
6. 团队协作中怎么避免“AI 代码孤岛”
6.1 统一 AI 生成代码的提交规范
个人使用 AI 写代码,影响范围有限;团队里每个人都用 AI 写代码,如果没有规范,很快就会变成“代码孤岛”——每个人生成的代码风格、结构、依赖选择都不一样,互相之间难以维护。
我们团队目前的做法是:AI 生成的代码必须在提交信息里注明,并且注明理解程度。比如提交信息里加一行[AI-assisted] core logic reviewed, edge cases pending。这不是为了追责,而是为了让 review 的人知道该重点看哪里。
另外,我们约定了几条底线:AI 生成的代码不能直接合并到主分支,必须经过至少一个人工 review;涉及核心业务逻辑的 AI 代码,必须由模块 owner 亲自 review;AI 引入的新依赖必须经过团队讨论。
6.2 定期做“AI 代码消化会”
这个做法可能听起来有点重,但效果很好。我们每两周会抽一个小时,挑出最近 AI 生成代码比较集中的模块,大家一起过一遍。不是逐行 review,而是让生成者讲清楚:这段代码解决了什么问题、核心流程是什么、有哪些地方自己还没完全理解。
讲的过程本身就是消化过程。很多时候,生成者自己在讲的时候就会发现“咦,这里我好像确实没搞明白”。其他人也会从不同角度提出问题,帮助发现盲区。这个会的目标不是找 bug,而是把个人理解变成团队理解,避免某个人离职或转岗后,他生成的 AI 代码变成无人能懂的“遗产”。
6.3 建立项目级的“AI 使用边界”文档
每个项目对 AI 的接受程度不一样。有的项目是全新项目,没有历史包袱,AI 可以放开用;有的项目是维护多年的老系统,架构约束多,AI 生成代码需要更谨慎。我们现在的做法是:每个项目维护一份简单的“AI 使用边界”说明,写清楚哪些模块可以用 AI 主导、哪些模块必须人工编写、AI 生成代码需要额外注意哪些项目约定。
这份文档不需要很长,一页纸就够。但有了它,新加入的成员就知道该怎么用 AI,不会因为“不知道项目规矩”而生成一堆需要返工的代码。
7. 我踩过的三个坑和对应的解法
7.1 坑一:让 AI 重构自己看不懂的代码
有一次,我接手了一个 AI 生成的模块,觉得结构不太清晰,就让 AI 帮我重构。结果重构后的版本更复杂了——它引入了一个抽象层,把原本直白的逻辑包装了好几层。我花了一个下午才勉强理解,最后发现性能还下降了。
教训:如果你看不懂一段代码,不要直接让 AI 重构。先自己花时间理解它,或者让 AI 解释它。理解之后再决定要不要重构,以及怎么重构。让 AI 重构你看不懂的代码,相当于让一个你不了解的人帮你整理一个你不了解的房间,结果大概率是东西找不到了。
7.2 坑二:过度依赖 AI 生成的测试
我曾经很依赖 AI 生成单元测试,觉得覆盖率高就安心。直到有一次,一个边界条件在测试里被“正确”覆盖了,但断言写的是错的——AI 生成的断言只检查了不抛异常,没有检查返回值是否正确。这个测试一直通过,但实际逻辑早就偏了。
教训:AI 生成的测试可以帮你发现遗漏的场景,但断言必须自己写或者仔细审查。测试的价值在于断言,不在于覆盖率数字。现在我的做法是:让 AI 生成测试用例的“骨架”(输入和预期场景),断言部分自己补。
7.3 坑三:在关键路径上使用 AI 生成的“聪明”代码
有一次,AI 在一个支付相关的模块里用了一个很巧妙的位运算来优化性能。代码很短,看起来也很优雅。我当时没完全理解,但觉得“AI 应该不会错”,就合并了。结果上线后发现,在某些特定输入下,位运算的结果和预期不一致,导致金额计算错误。
教训:关键路径上的代码,优雅不是第一优先级,可理解才是。如果一段代码你不能在五分钟内给同事讲清楚,那它就不应该出现在支付、权限、风控这些关键路径上。后来我把那段位运算改成了直白的 if-else,性能差异可以忽略,但可读性和可验证性大幅提升。
8. 把“看不懂”变成“看得懂”的日常习惯
8.1 每天留出“代码消化时间”
我现在每天会留出半小时到一小时,专门用来消化当天 AI 生成的代码。不是 review,而是真正地读——打开文件,从头到尾看一遍,遇到不懂的地方就查、就问、就画图。这个习惯看起来占用了“写代码”的时间,但它减少的是后期调试和救火的时间。
实测下来,这半小时的投入产出比很高。很多潜在问题在消化阶段就被发现了,不用等到测试甚至线上才暴露。而且,消化过的代码在后续修改时,速度会快很多,因为你知道每个部分在哪里、为什么在那里。
8.2 维护一份“AI 代码理解笔记”
我会用一个简单的 Markdown 文件记录每天消化的 AI 代码,格式大概是:模块名、核心流程一句话、关键决策点、还没搞懂的地方、后续要验证的点。这个笔记不追求完整,关键是把隐性的理解变成显性的文字。
写下来的过程本身就是梳理思路的过程。很多时候,你以为自己懂了,一写才发现逻辑接不上。这份笔记也是后续排查问题的索引——线上出了故障,先翻笔记看相关模块的理解记录,能快速缩小范围。
8.3 定期做“无 AI 编码”练习
这个建议可能有点反直觉,但我觉得很重要。每隔一段时间,我会刻意关掉 AI 辅助,自己从头写一个完整的小模块。目的不是否定 AI,而是保持自己的编码能力和架构思维不退化。
AI 可以帮你写代码,但不能帮你思考。如果你长期只做“审查 AI 代码”的工作,自己的设计能力、调试能力、对语言特性的敏感度都会慢慢下降。定期的无 AI 练习,就像健身里的“自由重量训练”,能保持你的基础力量。
9. 关于 AI 编程,我现在的真实心态
用了半年多 AI 编程工具,我的心态从最初的“太爽了,以后写代码不用愁了”,变成了现在的“它是个很强的助手,但方向盘必须在我手里”。AI 确实改变了写代码的方式,但它没有改变软件工程的本质——理解问题、设计解决方案、控制复杂度、保证正确性,这些核心能力依然需要人来承担。
我现在会把 AI 当成一个知识面很广、手速很快、但缺乏项目上下文和业务判断力的搭档。它负责快速产出,我负责理解、审查和决策。这个分工下,效率依然比纯手写高很多,但掌控感回来了。
如果你也在经历“AI 写代码很快,我却越来越看不懂系统”的阶段,我的建议是:慢下来,把理解放在第一位。生成代码的速度不是竞争力,理解代码的速度才是。一段你真正理解的代码,哪怕写得笨一点,也比一段你不理解的“优雅”代码有价值得多。