☰
AI改代码搞崩整个系统?关键在守住全局契约与公共边界
2026/10/15 10:40:16 网站建设 项目流程

1. 现象复现:AI把功能做完了,系统却崩了

先讲个真事。前两天我让AI写一个订单导出的Excel功能,从生成代码到接入接口大概用了十分钟,表面一切顺利。合并进主干的那一刻,CI直接全红,更诡异的是,旧的Excel上传接口全挂了,连带着几条统计报表也一起报错。排查到凌晨,最后定位到的原因,让我对着屏幕愣了好一会儿:AI在某个公共工具函数里,把一个默认参数从include_header=True悄悄改成了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。具体做法是:

  1. 先让AI生成一个独立功能模块,跑一次类型检查和静态测试。
  2. 如果通过,再让它写出接入该功能的胶水代码,但不允许改动旧文件。
  3. 接入后立刻跑该项目最核心的一条回归用例。
  4. 全绿之后,才让它继续生成第二个功能。

很多事故是因为“一口气让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在网里随便跑,跑得越快,你越省力。这张网,就是边界、验证和对系统契约的敬畏。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询