拓冰建站拓冰建站
首页 / 资讯中心 / 正文

AI编程代码膨胀失控?从提示词到评审的复杂度治理指南

项目概述AI编程已经不是“要不要用”的问题而是“怎么用才能不出事”的问题。我最近在重构一个中等规模后端服务时发现一个很讽刺的现象代码提交量比去年同期涨了将近一倍功能交付速度也确实快了但系统反而越来越难维护。新来的同事打开仓库第一反应不是“这代码写得真快”而是“这代码到底在干什么”。这让我意识到AI编程时代真正的风险点不是生成速度而是代码复杂性正在悄悄失控。这篇文章不聊“AI会不会取代程序员”这种虚的就聊一个非常现实的问题AI辅助开发让代码量暴增之后复杂性从哪些地方渗进来又该怎么用工程手段把它摁住。内容主要面向一线开发、技术负责人、以及所有正在用AI写代码但已经开始觉得“哪里不对劲”的团队。适合把这篇转给你的队友一起看尤其是那些觉得“AI写代码没问题有问题的是代码评审”的人。1. 为什么AI加持下代码反而更容易变复杂1.1 复杂性增量来自“只加不减”先讲一个我自己的观察。以前手写代码的时候因为打字是有成本的改一遍接口、删一段逻辑会下意识掂量半天这段要不要能不能合并是不是有更简单的写法。AI写代码之后这个“掂量”的过程被跳过了。你给它一句话它直接给你二十行修改建议你点击接受OK代码量又涨了。问题就出在这里。复杂性的本质是“系统中不可理解的部分在增加”。人类写代码时理解速度基本跟得上生成速度所以会不断做减法——删冗余、合并逻辑、提炼抽象。AI生成代码时生成速度远快于你的理解速度如果你没有在“接受”之前做一次慢速审查那每一轮AI辅助都相当于在系统里增加一坨你没有完全消化的复杂度。一年下来代码量膨胀30%到50%是非常常见的。我见过一个团队半年内仓库代码量翻倍核心模块的圈复杂度从平均5涨到14。这就像你家里请了个保洁阿姨她手脚特别麻利但你不在家的时候她把你所有东西都摆到台面上看起来是做了很多事可你回家之后发现家更不好住了。AI是那个麻利的阿姨复杂性就是那些堆在台面上的东西。你需要的不是“做更多事”的阿姨而是一个知道什么东西该收进柜子、什么东西该扔掉的阿姨。很遗憾现在的AI编程工具更像前者。1.2 模型的“局部正确”与全局失控AI模型生成代码有一个显著特点局部正确性非常高全局一致性比较差。你让它写一个函数它写得很漂亮边界条件、错误处理都考虑到了。但你让它在一个2000行的既有文件里新增一个功能时它倾向于在你指定的位置附近“打补丁式”地插入代码而不会站在整个模块的设计高度去考虑“这个功能是不是应该抽成一个新的服务”或者“这里是不是跟已有的某个函数重复了”。我实测过很多次。同一个模型你给它一个干净的、边界清晰的函数需求它生成的代码质量相当不错。但如果你让它在一段历史包袱很重的代码里加功能它会顺着现有的坏味道往下写把坏味道放大一遍。比如已有代码里充满if嵌套它会在嵌套里再加一层已有代码里有一堆魔法数字它会继续用魔法数字。你问它“能不能顺带重构一下”它会象征性微调一下但不会真正解决结构性问题。这就是为什么AI编程时代“代码评审”比“代码生成”更重要。模型天然偏向局部最优你必须在全局层面做约束和校正。换句话说AI负责解决“这段代码怎么写”的问题你得负责解决“这段代码该不该以这个形式存在于这个位置”的问题。1.3 上下文窗口带来的“记忆断层”还有一个容易被忽视的点上下文窗口。主流AI编程工具的上下文通常能装下几个文件的内容但装不下一个中型项目的全貌。当你让AI修改某个模块时它看到的只是模块的一部分它不知道这个模块在系统里扮演什么角色不知道调用方的期待是什么不知道周边的约定是什么。于是你会遇到这种情况AI在文件A里新增了一个函数返回格式是{code: 0, data: ...}而项目其他模块的约定是直接返回业务对象由统一封装层处理。这个不一致在AI生成的单个文件里完全看不出来但放到整个系统里就是个Bug——而且是个很难查的Bug因为编译能过、测试不一定能覆盖到、运行起来才会在某个角落炸掉。上下文断层导致的问题是“系统性遗忘”AI不是故意写错而是它“看到的”和“应该知道的”之间有落差。这也是为什么我越来越强调AI编程的提示词里上下文信息比指令本身更重要。你要花时间告诉AI这个模块的约定、相邻文件的职责、全局的错误处理方式而不是只甩一句“帮我把这个功能加上”。2. 复杂性失控的典型表现这些症状你八成见过2.1 重复代码的“无限复制”AI最擅长也最爱干的事就是复制粘贴式编程。遇到类似的需求它不会去“寻找已有的抽象”而是直接在当前位置重新生成一段功能几乎一样的代码。为什么因为生成全新代码比分析既有代码再复用要容易得多模型的注意力机制也让它在“参考某个文件”时不如“重新写一段”来得流畅。我见过最夸张的例子一个项目里有七个地方需要把时间戳转成日期字符串七个函数贴出来放到一起除了变量名不同核心逻辑完全一致。这就是AI干的好事。更麻烦的是当你修改其中一个的格式时你不会记得另外六个也调用同样的逻辑然后线上出现“同一个页面有的地方显示2024-01-01有的地方显示2024/01/01”的诡异问题。重复代码是复杂性的温床。每一份复制都意味着将来要在N个地方同步修改每一处同步遗漏都是一颗定时炸弹。人类写代码的时候多少会有点“不要重复自己”的洁癖AI没有这种洁癖或者说它根本“意识”不到自己在重复。2.2 死代码、僵尸依赖与“过度设计”并存死代码是另一个重灾区。AI生成代码时为了让方案显得完整经常会把所有可能的分支都写出来哪怕某些分支在当前业务上根本不会走到。你如果不对照业务需求逐行审查这些“防御性代码”就会被当作“考虑周全”的成果留在代码库里。更隐蔽的是僵尸依赖。AI在生成代码时会自动导入它需要的库但不会清理不用的导入。我在一个项目里看到AI建议引入了一个JSON解析库结果只是为了让某个字典对象的序列化更“顺眼”而项目里明明已经有三个功能完整的JSON工具类。这类东西不会立即造成故障但会让依赖图越来越臃肿构建时间越来越长安全审计的范围越来越宽。还有一种情况是“过度设计”。AI有时候像是一个理论书籍读多了但实战经验不足的初级工程师动不动就想引入设计模式,一句话的需求给你整出接口、抽象类、工厂模式三件套。在一个内部工具模块里搞这么多层抽象最终的结果就是“代码确实很规范但没人改得动”。2.3 超长函数、深嵌套与神秘命名这三个是“非AI时代”的老问题但AI让它们变得更加普遍。给你看一段我实际处理过的代码async function handleOrderProcess(req: Request, res: Response) { // ... 中间省略 800 行 if (user) { if (order) { if (inventory 0) { if (paymentSuccess) { // 实际处理逻辑 } else { res.status(400).send(payment failed); } } else { res.status(400).send(out of stock); } } else { res.status(404).send(order not found); } } else { res.status(401).send(unauthorized); } }这就是AI在一个“处理订单”的需求下生成的典型结果——它把用户校验、订单校验、库存校验、支付结果处理全部塞进一个函数里用四层if嵌套表达。逻辑上没有错但它其实应该拆成守卫子句先判断用户不存在就返回401再判断订单不存在就返回404逐层提前返回函数一下子就从三层嵌套变成一条直线。嵌套还不是最要命的最要命的是命名。AI给变量命名永远倾向于长、泛、通用什么data、tempData、resultData、transformedResult一段代码读下来像在读俄罗斯套娃说明书。你根本分不清哪个data是哪个data。这类问题的核心危害在于“认知负荷”读这种代码时你的工作记忆会被无关细节塞满很难看到真正的业务链路。认知负荷越高出错的概率就越大。3. 控制复杂度的核心方法从提示词到代码评审3.1 AI提示词必须显式声明“复杂度约束”很多人把AI编程提示词当成“跟实习生交代任务”只说“做什么”不说“怎么约束”。但如果你不告诉AI要控制复杂度它一定会往复杂里写。我自己整理了一套提示词模板让AI在生成代码之前先明确约束条件请修改 src/order/process.ts 中的 handleOrderProcess 函数满足以下需求 1. 新增会员折扣计算折扣规则见 src/discount/rule.ts 中的 applyDiscount 函数。 2. 复用已有工具函数 formatMoney禁止新增同名或功能重复的函数。 3. 函数拆分如果修改后函数体超过 60 行请拆分成多个纯函数并在函数名中标注职责。 4. 禁止修改对外接口签名禁止新增第三方依赖。 5. 代码风格请遵循项目已有的 ESLint 和 Prettier 配置。 6. 逻辑需要与现有代码的类型约定保持一致业务类型见 src/types/order.ts。注意第2、3、4条这三条是“复杂度护栏”。你明确告诉AI能复用就复用、别写太长、别有额外的抽象和依赖。实测下来带上这些约束后AI生成代码的平均质量有明显提升。至少过去那种“一次生成直接拉到三层嵌套”的情况少了很多。如果你不写这些约束AI默认的优化目标是什么是“正确实现用户需求”复杂度不是它的优化目标。所以你必须把复杂度约束从“不可见的需求”变成“可见的指令”这比你写多少“请写出优雅代码”之类的空话都管用。3.2 代码评审从“对不对”升级到“复杂度是不是合理的”过去代码评审重点看的是“逻辑对不对”“会不会有并发问题”“边界条件有没有处理”。现在有了AI之后逻辑正确性大幅提升——AI很少在纯逻辑上犯低级错误但它在“结构好不好”“是否应该放在这个位置”“是否应该引这个依赖”上的判断力很差。所以评审的重心必须转移。我在团队里推行一套“复杂度评审”清单每个PR强制过一遍新增代码有没有和已有功能重复的实现如果有为什么没有复用改动是否扩大到了“完成需求所必需”的范围之外比如明明只加一个字段却动了数据库迁移。新增抽象是不是必要的一个接口只有一个实现那这个接口在当前阶段是否有存在意义函数规模有没有突破模块内已有的平均行数标准是否为了“让AI容易生成”而牺牲了代码的可读性这个很常见AI在生成时会尽量按标准模式写反过来把业务逻辑扭曲成标准模式。这个清单不是让你对AI生成的所有代码都挑刺而是强制大家在做评审时把“复杂性成本”和“功能收益”一起放到台面上。如果一个功能确实需要拆成多个模块那也就是合理的必须在这种“复杂度评审”的框架下逐步推进。如果一个改动确实需要引入新的依赖我在评审单上会先问一句“这个依赖引入后谁将来负责维护”3.3 复杂度预算像控制预算一样控制代码膨胀工程上可以把复杂性当作一种财务预算来管理。团队明确约定每个模块的圈复杂度不超过某个阈值、每个函数的行数上限、每个新功能允许带来的代码行数净增量。听起来有点死板但它非常有效在AI时代尤其有效因为它相当于给“无限生成能力”装了一个水龙头。我自己的一个后端项目采用了如下规则单个函数体不超过80行超出必须拆分。单个文件的圈复杂度以ESLint的复杂度规则计算不超过15。新增功能时如果代码净增行数超过500行必须拆分成多个PR并在描述中说明拆分思路。重复代码率用jscpd检测超过3%时CI直接失败。这套规则刚推的时候团队里有人觉得“太苛刻了”但一个月后大家开始感受到它的价值代码评审更轻松了因为大PR变少了bug定位更快了因为函数边界清晰了新成员上手更容易了因为不需要在800行的大函数里做考古。AI编程就像是给团队的代码生成能力加了十倍杠杆但你得先给这个杠杆配上“预算仪表盘”。没有预算意识杠杆越高摔得越惨。4. AI编程场景下的工程护栏从git worktree到自动化检查4.1 用git worktree隔离AI实验性改动AI帮你改代码的时候有时候会“顺手”改出一堆你根本没预料到的东西。今天你只是想让它优化一个函数它给你把旁边的两个函数也重构了还换了一种你之前没用过的写法。这种改动如果直接跑到当前分支上很危险。你怎么知道它顺手改的那两个函数是变好了还是变坏了我的做法是所有AI大规模重构都放到独立的git worktree里做。git worktree可以让你在同一个仓库上同时checkout多个工作目录每个工作目录对应不同的分支。这样你可以在一个干净的目录里让AI折腾改完看效果满意了再合并不满意了直接删目录不污染你正常开发的分支。具体操作示例# 从 main 分支创建一个独立工作树用于 AI 重构实验 git worktree add ../reorder-ai-refactor -b refactor/order-process # 在 ../reorder-ai-refactor 目录里用 AI 工具做重构 # 改完后回主开发目录review 并测试 cd ../main-project git diff main..refactor/order-process --stat你还嫌不够还可以再加一个脚本在AI工作目录里跑完测试、lint、类型检查后把结果输出成一个summary。只有summary全部通过才值得回主目录去人工review这份改动。4.2 自动化检查是AI生成代码的“第一道安检”代码评审不能事无巨细地人工检查所以自动化检查的优先级被提到了空前高度。AI让你能在一天内生成大量代码人工评审的速度根本跟不上但机器可以。我常用的自动化护栏组合类型检查TypeScript严格模式或Python的mypyAI代码经常会在类型上“模棱两可”严格类型检查能抓住大多数接口签名不匹配的问题。ESLint/ruff 复杂度规则就像我前面说的限制函数长度、嵌套深度、圈复杂度。jscpd检测重复代码重点盯“AI复读机”问题。测试覆盖率门槛核心模块的覆盖率必须保持在80%以上AI生成的新代码如果没有对应的测试直接按未完成处理。依赖审计新增第三方包必须显式申报不能默默混进。这套组合拳的价值是什么它能把AI生成代码的“低级问题”全部拦截在PR提交之前让人工评审聚焦在更高层次的设计和业务语义上。按我实际数据统计给了AI套上这套护栏之后被评审打回的PR率从38%降到了16%。4.3 嵌入式和硬件场景的额外防线这里要特别提一下嵌入式开发领域。现在很多单片机比如STC系列也支持AI在线编程辅助但嵌入式代码对复杂性的容忍度比纯后端更低。寄存器操作、中断处理、时序控制这些代码每一行都跟硬件强耦合AI不懂硬件手册里那些“时序要求”是写在哪个寄存器的哪个bit上的。嵌入式AI编程必须具备比纯软件场景更严格的护栏代码生成后必须做“硬件在环”测试不能只依赖单元测试。对全局变量、中断函数、内存操作做专项review这些地方AI最容易在不该动的地方“改进”。对编译告警零容忍嵌入式编译器对类型转换的告警往往意味着硬件层面的隐患。我之前处理过一个用AI辅助写的串口驱动代码从逻辑上看完全正确但实际跑起来偶尔丢字节。查了半天发现AI在中断处理函数里加了一句延时——它以为这样能缓解总线冲突实际上却破坏了中断时序。这种问题只有在“懂硬件”的前提下才能在review时拦下来AI自己永远发现不了。5. 实操中的排查与度量用数据证明“复杂性失控了”5.1 如何量化代码复杂性要控制复杂性你得先能度量它。我推荐三个最实用的指标不需要引一堆学术界的概念在工程上就能直接执行。第一个是圈复杂度最简单的理解是“代码里有多少条独立路径”。路径越多测试越难覆盖人脑越难跟踪。ESLint一键能算出来超过阈值的函数拆掉。第二个是认知复杂度比圈复杂度更“像人”。它不光看路径数量还看嵌套深度、逻辑连串、跳转。一个函数圈复杂度只有5但五层嵌套读起来比圈复杂度15的函数还累。SonarQube等工具可以自动计算。第三个是代码行数与“净新增/净删除”的比例。这个数据在AI时代尤其重要。我们在Git提交信息里强制标注新增行数、删除行数如果一个功能正常来说只需要200行净增你交了个1500行净增的PR那不用看代码内容大概率已经出了复杂度问题。我习惯每个月做一次全局扫描把“高复杂度函数TOP20”导出用表格对比上个月排名前三的函数这个月有没有拆掉AI新增的函数有没有在各个模块之间分配不合理的情况这些数据要拿到团队会上别扯感觉数据和排名摆出来大家都服。5.2 一次“复杂度大扫除”的完整过程我来复盘一个真实案例。上季度我们对支付模块做了一次复杂度治理背景是AI重构过的支付回调函数已经膨胀到900行圈复杂度32线上出了两次“改了A分支影响B分支”的故障。过程分四步。第一步量化基线。用ESLint加SonarQube扫描整个模块输出每个文件的认知复杂度、重复率、函数最大行数。得到基线数据后存下来。第二步拆解大函数。挑出900行那个回调函数按业务顺序拆成6个纯函数校验签名、解析订单、检查库存、执行扣款、生成流水、发送通知。每个函数控制在60行以内参数不超过4个。这里特别说明拆的时候不要顺手修复任何业务逻辑问题只做结构调整保持行为完全一致。把“行为变化”和“结构变化”分开排查问题才容易。第三步消除重复。用jscpd报出来的重复代码块逐个确认归属能合并的合并成公共函数。这一步要注意公共函数的入口要稳参数要少不能把一堆半毛钱关系没有的逻辑硬塞进一个“util”里。好的抽象是“人话读得懂的抽象”不是“看起来规范实际没人看得懂的抽象”。第四步建立回归防线。整轮重构完成后跑全量测试确保测试通过然后提交一个“本次重构零行为变化”的记录并让同事在评审时逐行对照旧代码和新代码确认没有偷偷改逻辑。最终结果支付模块的平均认知复杂度从18降到9单次故障排查时间从平均4小时缩短到1小时以内。最重要的是后续再用AI给这个模块加需求时AI生成代码的质量起点变高了,它在一个干净的结构上生成代码比在一堆乱麻里打补丁要靠谱得多。5.3 可观测性让复杂性“看不见的手”现形代码复杂性的危害往往不直接体现在“代码好不好看”上而是体现在“线上问题能不能快速定位”。我强烈建议团队把可观测性当成复杂度治理的盟友。日志结构统一、链路追踪完整、关键业务指标有监控这些都能让你在复杂代码爆炸时不用靠“人肉翻阅代码”来猜问题。反过来如果你的代码已经复杂到他妈都不认得但日志、trace、监控都很清晰那你还能在“代码混乱”和“系统可用”之间维持一个脆弱的平衡。可观测性是复杂性的“缓冲垫”。实测下来AI帮我写这种可观测性基础设施的代码效率特别高因为它很适合生成那种“模板化”的logger、tracer封装。用好AI生成可观测性基建再用可观测性系统来兜底AI生成的其他代码这有点“以子之矛攻子之盾”的味道但确实管用。最后分享一个日常工作中的小习惯每完成一个功能看一眼这次提交的行数统计。超过一千行净增的改动无论AI写了多少都强制自己去读一遍核心链路。我见过太多人把AI生成的长代码直接合并让代码库不断膨胀却忘了最后真正为代码负责的是人不是模型。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门