Composer 2.5规划编程陷阱:单元测试全绿为何代码仍出bug

本文揭露AI编程助手Composer 2.5在严格遵循开发计划时仍产出错误代码的深层原因。通过拆解单元测试全绿但集成失败的案例,提出必须增设双重校验机制:自动化的AI行为规则与人工维护的集成检查清单。核心是解决“功能完整”与“生产就绪”之间的鸿沟,防止局部逻辑正确但全局配置错误的典型故障。

别信AI的“做完”信号!计划执行完美却出错的根源



你有没遇到过这种邪门事

AI帮你写代码。计划列了八页。单元测试跑了一百多个。全绿。绿得晃眼。你信心满满点了个发布。结果用户一用就崩。而且崩得还特别有逻辑——不是随机闪退,是那种“明明每个零件都合格但整车就不走直线”的憋屈法。

我今天就跟你们聊聊这个事。我用的是Cursor里的Composer 2.5,写Rust后端。这哥们按计划干活从不偷懒。步骤拆得比我家冰箱格子还细。写完还自己写测试。但就是交出来的代码,总带着那种“在学校考满分、一上班就捅娄子”的气质。

后来我跟它聊了半宿。不是吵架,是真的像两个老油条复盘事故现场那样聊。聊出来的结论让我后背一凉:问题不在“没做对”,而在“做完了”这三个字本身。AI判断“做完”的标准,跟我们人类上生产线的标准,压根不是同一本字典。



单元测试全绿的时候往往最危险

先给你们讲个真实翻车案例。

AI写了一个文件路径处理模块。单元测试里面传的都是绝对路径,比如 /tmp/my_project/config.json。测试全部通过。完美。但实际跑的时候,配置文件用的是 workspaceRoot: “.”,也就是当前目录相对路径。路径拼接就变成了 ./tmp/my_project/config.json。这种地址谁都打不开。

单元测试为什么没抓到?因为它用的是固定绝对路径。而生产配置用的是相对路径。这两条路径在测试代码里从未交叉过。就像你测试雨伞防水的时候一直淋的是冷水,结果出门遇到的是硫酸雨。材料特性完全不同。

AI不是笨。它是没被要求去验证“配置格式变化后,所有路径函数依然工作”。计划里只写了“实现路径解析函数,并编写单元测试”。至于这个函数跟全局配置之间的婚姻关系是否牢固,计划里没写,所以AI也没测。

还有一个更隐蔽的例子。系统里有个开关叫 checkpoints。配置里存了一份 config.checkpoints.enabled,状态里又存了一份 state.checkpoints_enabled。用户用 /checkpoints on 命令可以打开开关,这个只改了状态里那份。换到 /mode ship 命令时,AI忘了同步配置里的那份。结果界面显示开关已开,底层逻辑读的却是关。两边各自看都正常,凑一起就变成薛定谔的断点。

这就像你跟你媳妇各记各的银行密码。你记的是123456,她记的是654321。各自都没记错。但去取钱的时候谁也取不出来。每段代码单独看都是模范员工,组合起来就是个乌龙剧组。



绿色测试告诉你的只有一件事

测试全绿只说明一件事:你预设的那些小场景,函数都应付过去了。但它没告诉你的是:这个函数放在真实环境里,跟其他模块、配置文件、命令行参数搅和在一起时,会不会当场翻脸。

很多程序员把“单元测试全绿”当成“可以发布了”的信号。这就像体检报告说你血压正常,你就觉得自己能跑马拉松。体检只查了静态指标。马拉松考验的是动态系统配合。

AI也是这么被教的。它的训练数据里,大量案例都是“写完函数、写测试、测试通过、任务完成”这条标准流水线。很少有案例说“测试通过后,还要故意拿不同配置跑一遍,看会不会炸”。因为人类工程师自己都经常跳过这步。

更麻烦的是,AI有一种“防御性加码”的毛病。我看到它写的代码里有一段检查路径是否越狱的逻辑。它已经有一个函数叫 resolve_under_workspace(),专门防止路径跑到工作区外面。这个函数没问题。但AI不放心,又在另一个地方加了一段 starts_with 检查,用的却是另一种路径表示格式。一个用的是规范化的绝对路径,另一个用的是原始相对路径。两段检查各自都对,但放在一起等于没检查。因为相对路径永远不以绝对路径开头。

这叫“腰带加背带,结果扣眼不对齐”。每个安全措施单独看都很负责,凑一起就成了安全漏洞。单元测试不会测这种,因为它只测单个函数。它不测“函数A的输出,是不是函数B期待的输入格式”。



计划越详细越容易掉进同一个坑

我以前觉得,让AI写详细的实施计划就能解决问题。十二个步骤,每个步骤带验收条件。结果呢?步骤全走完了,验收全过了,代码还是烂的。

原因很简单。计划里的验收条件,全是“函数做没做某件事”。没有一个是“配置变了之后,函数还做不做那件事”。也没有一个是“用户切换模式之后,状态跟配置还一不一致”。

这就是“功能完整”和“生产就绪”的区别。功能完整是“我把该有的零件都装上了”。生产就绪是“在各种真实操作顺序下,这些零件都不会互相绊倒”。

AI是按照“功能完整”来打工的。你把需求清单给它,它就勾清单。至于清单之外的“默认配置shape是什么”“切换模式会不会留垃圾状态”“新建的未跟踪文件会不会被忽略”,这些东西不在清单上,AI就默认不用管。

你不是在骂AI蠢。你是骂它把“做完”理解成了“写好代码并让单元测试通过”。而你要的“做完”是“在真实使用的各种骚操作下依然稳如老狗”。

这两个“做完”之间的距离,就是每次发布后半夜爬起来修bug的距离。



计划里的好矩阵不等于显眼的验收项

很多计划的“测试矩阵”写得特别漂亮。什么操作系统版本、数据库类型、并发数,一排组合。但往下翻到验收条件时,就变成一句“所有单元测试通过”。

那个漂亮的矩阵,跟AI最终执行的验收,中间没有强制连接。AI看了矩阵,哦,这是信息参考。然后扭头去看验收条件,哦,只要测试全绿就行。矩阵里的“相对workspaceRoot”“模式切换后断点状态”这些场景,AI根本没有被要求写成测试用例。

这就像你给了厨师一份二十页的菜品品鉴指南,但最后跟他说“上菜的标准就是锅铲敲三下”。他当然只敲三下。前面那二十页他当小说看了。

关键问题是:AI不会主动把矩阵里的场景翻译成验收条件。你得明确写进“做完的定义”里。而且这个定义不能藏在文档深处,得放在每次AI准备说“我做完了”之前,必须扫一眼的地方。



最冤的那种bug叫“两套真相没接线”

我们再说深一层。很多集成bug的本质是:同一个概念,在两个地方各存了一份拷贝,然后只同步了一边的代码。

比如那个checkpoints开关。配置里存了一份,运行时状态里存了一份。用户通过命令行改开关时,改了状态。但/mode ship这条命令,不知道还需要去同步配置。两边各过各的日子。

这类bug有个共同特征:每条代码路径单独测试都是绿的。因为单独测试的时候,你只测命令A的逻辑,不会同时测命令B跑完后命令A还正不正常。你只测配置加载,不测配置加载后还被其他模块偷偷改了。

AI写代码的时候,倾向于在每个局部做“最安全的处理”。比如在checkpoints命令里,它觉得“改状态就是改状态,不用碰配置”。在mode命令里,它觉得“改模式就是改模式,跟断点开关无关”。每个局部决策都合理。合在一起就炸了。

这就像装修。电工说我把灯装好了,亮。水管工说我管子接好了,不漏。但你一开水龙头,灯灭了。因为电工和水管工没人去确认过“地线和水管是不是共用了一根接地桩”。各自专业范围内都没错。跨专业就翻车。

AI缺的不是专业能力,缺的是一个“在你宣布做完了之前,必须把跨模块场景跑一遍”的门槛。



防御代码的陷阱在于看着越安全越容易出事

我看到AI写了一段特别负责任的路径检查。它先调用规范化函数,把路径变成绝对路径。然后又写了一段 starts_with 检查,防止路径越狱。看着双保险对吧。

问题在于,starts_with 检查用的路径是原始输入,还没规范化。原始输入可能是“.”,而规范化后的绝对路径是“/Users/me/project”。一个点号,怎么可能以“/Users”开头呢?所以这个检查永远不触发。它觉得自己保护了系统,其实啥也没干。

为什么AI会写出这种代码?因为它看到了“路径越狱”这个风险点,想多加一层保护。但它没注意到两段保护用的数据形态不同。单元测试里,它传的是绝对路径,两个检查都能过。实际运行时,传的是相对路径,第一段检查通过后把它转成绝对路径,第二段检查因为数据还没转就直接失效了。

这就是典型的“腰带加背带但扣眼错位”。每个措施都在,但链条断了。单元测试测不出这个,因为单元测试里的输入是精心挑选的、恰好让两个检查都生效的例子。真实输入是混乱的、会暴露对齐问题的。

AI需要被明确告知:多加一层检查之前,必须验证这层检查跟原有检查用的是同一套数据格式。而这个要求,不会自动出现在任何标准计划模板里。



缺少的那张表叫做“集成矩阵”

我们团队后来总结了一张表。不是什么高科技,就是一张纯手工维护的检查清单。每次有新功能上线前,人肉把这表里的场景跑一遍。

表里有什么呢?

第一种场景是“默认配置形状”。很多AI写的代码假设配置文件里一定有个绝对路径。但默认配置给的是相对路径“点”。跑不跑得通?得专门测。

第二种场景是“模式切换后状态残留”。比如你先用explore模式,再切成ship模式。前一个模式改过的全局标志,后一个模式会不会尊重?不会的话,就得加同步逻辑。

第三种场景是“工具干跑”。有些工具支持dry-run模式,就是假装干活但不真改文件。AI经常把dry-run的结果当成真实结果来处理。你得专门测一下,dry-run报告说“成功”的时候,是不是真的没碰任何文件。

第四种场景是“新文件和未跟踪文件”。Git diff之类的命令默认只关心已跟踪文件。但你的脚本可能要处理新建的还没add的文件。AI写的代码经常忽略这种边界。

这张表不是给AI看的。是给人类自己看的。但关键是你得让AI在说“做完了”之前,必须读一遍这张表,并且把表里每个场景都写成一个具体的集成测试用例。

你可能会说:那每次都得写这么多测试?不是写测试。是跑场景。很多场景其实你人肉点两下就发现了。但AI不会主动去点,因为它没有“切换模式后手工点一下界面”这个动作。所以你得把它变成自动化的验收条件。



两条防线比一条防线管用但多数人只设了一道

现在说解决方案。其实就两层。

第一层是自动规则。你要在项目里放一个特殊文件,告诉AI:每次你准备说计划做完了、可以收工之前,必须先读那张集成矩阵表,然后把表里的场景转换成具体的验收代码,最后确认跨模块的那些测试是真写了,而不是只写了单元测试就跑。

这个规则文件放到项目的 .cursor/rules 目录下面。设置 alwaysApply 为 true。这样AI每次会话都会自动加载这个规则。它不需要你每回都重复一遍“别忘了那个表”。

第二层是人工维护的检查清单。就是这个集成矩阵表本身。放在 docs/INTEGRATION_MATRIX.md。每次线上出个新类型的集成bug,你就往这个表里加一行。比如“发现相对路径和绝对路径混用导致越狱检查失效”,你就加一个场景叫“workspaceRoot 是点时所有路径函数正常工作”。

这个表会随着项目成长。不是静态的。每次事故复盘,你就往表里添一行。AI下次就会按新规则验收。

这两层缺一不可。只有自动规则没有人工表,规则就不知道具体要检查啥。只有人工表没有自动规则,AI就不会自动去读它。你得自己记着每次验收前喊它去读。而你自己大概率会忘。



规则文件里面到底写了什么

我给你们看看我放进去的那个规则文件的内容。不贴代码,就说人话。

它干的第一件事是:在任何计划的“验收”步骤之前,强行插入一步叫“读集成矩阵”。这一步不是可选的,是必须的。

第二件事是:读完矩阵之后,不能只是看看。必须针对矩阵里的每一行,写一个具体的集成测试用例或者校验脚本。比如矩阵里写了“workspaceRoot 是点时路径解析不出错”,那就得写一个测试,专门把配置改成点,然后调用所有路径函数,看有没有炸。

第三件事是:不准把“单元测试全绿”当成“可以合并”的信号。单元测试只能告诉你模块内部没崩。集成测试才能告诉你模块之间没吵架。

第四件事是:如果某个集成场景没法自动化测试(比如需要手工切换模式后重启服务),那就必须在计划里显式标注“需要手工验证此项”,并且提供一个手工验证步骤。

这个规则文件不大,但它的作用相当于给AI装了一个“做完之前再过一遍安检”的强制流程。没有这个强制流程,AI就会按它的默认习惯走——也就是单元测试通过就交差。



你也可以给自己项目加一条个人规则

如果你同时在好几个项目里用AI,不想每个项目都复制一遍那个规则文件,还有个更省事的办法。

在Cursor的设置里,有个叫“用户规则”的地方。你放一条很短的全局规则进去,大意是:在任何项目里,当你要标记一个计划为完成之前,必须先检查该项目根目录下有没有 INTEGRATION_MATRIX.md 文件。如果有,逐条确认每项集成场景都被测试覆盖了。如果没有,提醒项目负责人建立这个文件。

这个全局规则不会替你解决所有问题,因为不同项目的集成场景差别很大。但它能确保你不在任何项目里忘记这回事。就像出门前摸口袋:手机、钥匙、钱包。摸完不一定万事大吉,但不摸大概率要折返跑。

对于单个项目来说,项目级的规则文件就已经够用了。你不需要全局规则也能跑通。但如果你和我一样记性差、项目多、每个月的“哎呀又忘了测那个”次数超过三次,那就两条都加上。



做完和跑通之间永远差一个集成清单

说回最开始的郁闷:为什么AI计划执行完美、单元测试全绿,代码还是有bug?

因为AI的“做完”定义里缺了一张集成清单。这张清单不在任何标准开发流程模板里。它来自你每次踩坑后的复盘。来自“上次为什么炸了”的那个具体原因。

你可以把这张清单存在文档里。但存在文档里没用,你得让AI每次完工前强制去读它。你得把“读清单”变成一个不可跳过的步骤,而不是一个“建议”。

这就像飞机起飞前的检查单。飞行员就算飞了一千遍,也得照着单子一项项念。不是因为傻,是因为大脑在熟悉的事情上最容易跳步。AI也是。它在一千次“单元测试通过就交差”的训练后,早就把这个跳步固化成习惯了。

打破这个习惯的唯一办法,不是在它脑子里修修补补,而是在它行动路径上横着放一块铁板。铁板上写着:读单子,每项测完,再说做完。

你加了这层铁板之后会发现,AI还是那个AI,代码还是那个代码。但那些“每个零件都对、整车不走直线”的邪门bug,就真的少了。不是AI变聪明了,是你终于让它承认了:原来我以为的“做完”,离真正的“跑通”,还差一张不断长大的集成清单。

极客辣评

使用 Opus 进行规划,使用 Composer 2.5 进行编码。