资讯动态

AI改代码搞崩整个系统?关键在守住全局契约与公共边界

发布时间:2026/10/15 10:40:28 来源:尧图企业网站定制
1. 现象复现AI把功能做完了系统却崩了先讲个真事。前两天我让AI写一个订单导出的Excel功能从生成代码到接入接口大概用了十分钟表面一切顺利。合并进主干的那一刻CI直接全红更诡异的是旧的Excel上传接口全挂了连带着几条统计报表也一起报错。排查到凌晨最后定位到的原因让我对着屏幕愣了好一会儿AI在某个公共工具函数里把一个默认参数从include_headerTrue悄悄改成了False。导出功能用上了新行为而上传、统计、邮件发送这些老调用方根本不知道这个变化全按False跑数据于是全崩了。这种场景真的太常见了。功能确实做完了AI甚至给你写了测试但整个系统却像被抽掉了一块积木一样开始晃。问题在于大多数人在这个时刻的第一反应是“AI不行”然后退回手写代码。但我的体会是真正的问题不是AI本身而是我们压根没有给AI设定一个清晰的“输出边界”也没有在合并之前建立一套针对系统完整性的检查关卡。AI写代码就像往一栋已经盖好的楼里塞新房间如果不知道承重墙在哪里新房间装修得再漂亮楼也可能出事。这篇文章我把这类事故的根因、我会怎么拆解、该怎么给AI下指令、以及合并前必须守住的关卡全部整理出来。不管你是刚开始用AI辅助写业务代码还是已经在一个复杂系统里反复让AI干活这套思路应该都能帮你少踩几个坑。2. 根因拆解AI为什么容易顾头不顾尾2.1 AI擅长局部填词不擅长全局架构大模型写代码的本质是基于上下文做“下一步最像样”的预测。也就是说它生成一个函数体、一段逻辑的时候会最大化地让这一段代码看起来合情合理。问题恰恰出在这里一段代码看起来合情合理不等于它在整个系统里不破坏别的东西。我经常把AI类比成一个特别会写段落但不太会写整本书的新人。你让它补全一个章节它能把这段写得行云流水但它不会主动去数前面三十章里都出现过哪些角色、哪些伏笔需要呼应。放在编码场景里就是AI很少会因为“我改了这个数据结构会导致另外六个模块的调用失败”而主动停下来说“我不该这么改”——它没有这个全局信息也通常不会主动去搜索所有调用方。比如你让AI“把Excel导出功能加一个分页参数”它大概率会直接改现有函数的签名或者干脆改变返回值的结构。局部来看这个改动非常合理但全局来看全项目里所有调用这个函数的地方都可能出问题。AI不是不负责它是真的“看不到那么远”。2.2 局部正确与全局契约的冲突任何成规模的系统都有一些隐性的“契约”。所谓契约就是模块与模块之间约定好的交互方式往小了说是函数签名、返回字段往大了说是消息队列的消息格式、数据库的表结构、前端接口的字段名。这些契约最怕“悄无声息的变化”。我见过最典型的一次事故AI在生成一个用户列表功能时把接口返回里的userId从字符串类型改成了数字类型。新功能跑得欢但前端老页面、数据分析脚本、移动端App全都在按字符串处理一瞬间几乎所有涉及用户信息的页面都出了问题。如果项目里有严格的后端类型约束编译器还能帮忙揪出来一些但如果AI是为了“适配另一个AI生成的代码”而主动改了契约那排查起来会非常痛苦。所以我会把所有变量、函数名、返回结构、依赖关系都看作系统的“血管”。AI每动一刀都可能是切在血管上。这是很多只看交付功能的人会忽略的一点。2.3 AI的“删除欲”与隐藏的运行时依赖AI还有一个特别坑的习惯动手清理“无用代码”。在它看来未引用的变量、没有显式调用的函数、长满注释的旧工具方法都是可优化对象。但在真实系统里很多代码不是“显式引用”而是被框架、反射、事件机制、装饰器动态调用的。比如一个模块里有一段module_loader.register(plugin_a)的调用AI发现这个调用之后没看到别的地方用plugin_a于是判定为“死代码”删了。但那个注册逻辑是给运行时的插件系统用的只有到启动时才会被反射扫描到编辑器里根本没引用。结果就是功能代码生成完美一启动系统就报插件缺失。我自己定了一个死规矩AI生成代码之后只要它动了删除操作就必须在review时高亮标注并且严格确认“这个文件/函数真的没有任何动态调用方”。这条规则帮我挡住了不下三次线上事故。2.4 提示词不够具体AI只能“自由发挥”最后还有一个原因也是最应该反思的。很多人让AI改代码只丢一句话“帮我把导出功能改成分页导出。”这个指令本身信息量极低。AI不知道你现有的分页边界是什么不知道哪些接口不能动不知道公共工具类是承重墙更不知道项目里有哪些调用方在依赖旧行为。所以AI只能靠自己对“合理实现”的理解去自由发挥。它不是刻意要改坏而是你的期望没有变成它的约束。想让AI少闯祸第一步不是换更贵的模型而是把提示词从“一句话需求”升级成“带边界和约束的工程指令”。这块我后面展开讲。3. 实操方法论让AI只碰叶子不碰树根3.1 动手前先圈定“受控区域”我这两年用AI辅助编码最大的经验就一句话**把AI限定在系统的“叶子节点”上干活。**什么是叶子节点就是那些没有其他模块依赖它的独立工具函数、组件、脚本、临时任务改动它的风险范围很小。反过来公共工具函数、基类、API层、数据库迁移脚本、配置中心这些都是“树根”AI一碰就可能导致整片树都动。所以我现在会让AI干两类活。一类是新增逻辑比如“新建一个独立的PdfExportHelper类给现有导出功能增加PDF输出”这类任务天然隔离。另一类是修改业务模块内部实现但前提是模块对外接口保持不变。如果需求确实要动公共函数或API层我不会让AI直接改而是先让AI把改动方案列出来列出改动影响的范围再在代码里做一次性修改。比如我会让AI先回答这几个问题涉及的公共函数/接口叫什么当前有哪些调用方如果改签名需要同步改哪些文件它在回答这些问题的过程里其实就把影响面摊开在桌面上接下来再决定怎么改。3.2 用依赖感知提示词替AI补上“盲区”很多人都知道提示词重要但大多数人给的提示词都是“任务式”的不是“系统式”的。任务式提示词只描述目标系统式提示词才描述约束和边界。我用的系统式提示词模板大概是这样的背景这是一个订单管理系统使用Python 3.11 FastAPI之前的Excel导出功能在utils/excel_utils.py中。 请你新增一个独立文件services/export_orders_paged.py实现订单分页导出。 约束 1. 不得修改utils/excel_utils.py中的任何函数签名或默认参数。 2. 新增文件可以 import utils.excel_utils但必须通过参数显式传递配置不要改变全局默认行为。 3. 接口入参和返回结构与现状保持一致除非用户明确要求破坏性变更。 4. 删除任何引用或代码时先明确说明被删除内容的现有调用方未经确认不删除。 5. 完成后在CHANGELOG.md中追加一段简短说明。这个提示词看起来很啰嗦但它是在给AI兜底。第一明确公共文件不可亵渎第二确保新增与旧逻辑隔离第三强制AI解释删除行为。你会发现输出质量完全不一样。AI害怕自己的删除行为被质疑就会主动去查调用关系至少不会随手“清理”了。3.3 每一步都跑“小步行进式回归”我喜欢把AI生成代码的流程拆成几步每次给一个非常小的任务然后立刻验证。这个思路类似于敏捷开发里的小步提交只不过交片的变成了AI。具体做法是先让AI生成一个独立功能模块跑一次类型检查和静态测试。如果通过再让它写出接入该功能的胶水代码但不允许改动旧文件。接入后立刻跑该项目最核心的一条回归用例。全绿之后才让它继续生成第二个功能。很多事故是因为“一口气让AI完成了三次改动然后一次性合并”中间任何一个环节出错排查范围都会被放大。我踩过三次这个坑之后就强制自己改成“一次一问、一次一测、一次一合”。比如上一节说到的Excel导出我现在会先让AI生成独立的分页导出模块跑单测再让我手动写一行接入代码我自己知道动了哪个文件风险完全可控然后跑核心回归。前后加起来不过多花十五分钟但系统崩掉的概率会下降一个量级。3.4 让AI自己读报错但你要读AI的diff还有一个很实用的操作拿到报错信息之后别急着亲自上手改把报错原样扔回给AI让它分析并修复。但有一个前提——AI给出修复之后你必须要打开git diff看它到底改了什么尤其是那些不在预期范围内的改动。我自己会重点扫三个位置一是看有没有修改别人调用的公共函数签名二是看有没有新增依赖三是看有没有删除旧代码。只要这三个位置出现预期外的变化就立刻喊停。4. 关键检查项从AI输出到上线前的关卡4.1 类型检查、静态分析和依赖审计AI生成的代码首要验证是让它能“安静地编译”。不管是TypeScript、Python还是其他语言先跑一遍类型检查和代码规范检查。这里不是要抓语法错误而是要借编译器来检查AI是否触碰了接口契约。比如在Python项目里可以跑mypy检查类型一致性在Node.js项目里可以跑tsc --noEmit。这类检查最厉害的地方是它能把“某个字段类型被改了”这种隐蔽问题暴露出来。另外一个很容易被忽略的是依赖审计——AI经常会顺手加入一个pip install或者改掉package.json里的版本。你要用锁文件diff来看它到底动了哪些依赖因为在真实项目里一个传递依赖版本被升级就可能导致整个运行环境行为漂移。4.2 基线回归测试是“不倒退”的保证如果你正在用AI大量生成业务代码那么一套基线回归测试就是保命的。所谓基线回归就是不管你新功能怎么加都要保证已有的核心流程不被破坏。我这边是把所有核心用例都按优先级分好P0是支付、订单、用户鉴权这类主干逻辑P1是主要报表和同步任务P2才是次要不紧急的功能。只要AI动代码我至少把所有P0和P1跑一遍全绿才敢合入。如果有项目的接口是公开出去的还会加上契约测试用类似openapi-diff的工具去对比接口定义前后变化一旦有破坏性变更直接报警。这套流程看起来很费时但实际执行速度很快——一般十分钟左右。重点是它能把“AI偷偷改了接口返回字段”这类问题直接拦在门外。没有这些测试托底的团队就像在没有安全网的工地上玩杂技一旦开始高频用AI生成大量代码迟早要翻车。4.3 发布前的“人机协同评审清单”就算前面全过了我最后手工review时还会对着清单走一遍这个习惯是从几次事故教训里硬逼出来的。清单大概是这样的本次改动是否涉及公共工具函数、配置类、数据库模型、接口层如果有盯着diff看。是否新增了额外的依赖必要性和版本来源是什么是否删除了任何文件或函数是否存在动态引用或反射引用是否修改了全局样式、全局变量、环境变量会不会影响其他模块是否闯入了自动生成的锁文件锁文件里有没有大版本跳变这条清单用久了会变成身体记忆。好在这种检查不需要很深的系统知识它只是帮你把AI代码里“超出任务范围的下意识行为”挑出来。5. 事故速查表AI改坏系统的经典场景与排查路径5.1 典型“AI把系统搞坏”的场景场景表现常见根因排查思路公共工具函数被改多个无关模块同时报错AI为满足新需求调整了公共函数签名/默认参数定位公共函数diff回滚公共层改动改用新模块适配API字段类型被改前端、脚本、App大量报错AI改了序列化字段类型或数据结构对比接口定义和序列化器diff检查契约兼容性“无用代码”被删启动时报找不到模块/回调动态导入、反射注册被当成死代码删除搜删除代码的引用名确认是否存在运行时注册机制依赖大版本被更新行为变化但无编译错误AI顺手改到package或requirements检查锁文件diff回滚不必要的依赖升级新增文件命名冲突功能时好时坏路径错乱AI生成的模块名与已经存在的模块相同搜索全局同名称模块命名加上功能前缀避免“撞车”5.2 快速定位的“二分法”如果真的遇到事故我的排查顺序大概是这样先看git diff把改动文件按“公共层”、“业务层”、“新增文件”三类分开。优先检查公共层的每一处改动——大多数“全系统莫名其妙炸了”的case都源于这里。再看有没有删除操作尤其是那种没有任何git log说明的删除。最后一个才是检查新增文件本身。有个小技巧是把改动拆成两个commit分别验证。第一个commit只保留新功能新增的文件第二个commit才包含接入旧代码的改动。这样一旦有问题你直接回滚第二个commit就行。这个流程非常机械但特别可靠哪怕你完全不理解AI改了什么也能在几分钟内让系统恢复。6. 让AI协作更稳的日常习惯6.1 先提升系统的“抗伤能力”再放开手用AI越是依赖AI生成代码你越需要一个边界清晰、可测试性强的系统底座。如果代码库本身就是一团互相纠缠的意大利面条AI每改一处都可能把另外三处带崩。与其寄希望于“下一个大模型更聪明”不如先把基础设施修得坚固一些。具体做法是多拆模块、明确契约、补充核心用例、约束公共层的变更流程。这些工作不会直接产生新功能但它们决定了AI生成代码的上限。就像你把墙壁隔好、承重墙标出来之后请谁来装修都不容易出大事故。如果一个团队准备大规模引入AI编码我第一建议永远是先补基线测试而不是先开AI工具账号。6.2 把AI当“可以批改的实习生”我现在的心态已经变了不再把AI当成会出错的工具而是当成一个手脚麻利但理解片面的实习生。实习生的特点是什么执行快但你不敢让他直接改公司的核心网关。所以项目里涉及公共逻辑的改动我会亲自下场把AI当顾问用——让它分析影响面、列出方案、生成建议代码但不让它直接写进主干。而新增功能模块这种相对隔离的活我会大胆交给AI让它快速出活我自己负责验收。这种分工方式既保证了速度又控制了风险。6.3 最后再分享一个很小的技巧建议给AI设定一个默认输出规范每次改完代码让它必须附上一段“变更说明”简单列出它动了哪些文件、有没有改公共逻辑、有没有新增依赖。就这个小小的要求能逼AI把隐式操作显式化。很多时候你自己可能没注意AI改了依赖版本但一旦它被要求声明变更它就会诚实写出来。我在实际使用中越来越明白一个道理AI的破坏力不是来自它不够强而是来自我们太信任它的“局部合理性”。再聪明的模型也看不到你系统的全局上下文而恰好在工程落地里全局上下文又比语法正确重要一百倍。你真正要做的不是停止使用AI而是建立一张能把风险兜住的安全网让AI在网里随便跑跑得越快你越省力。这张网就是边界、验证和对系统契约的敬畏。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑