接手一段 1,200 行的老订单模块,想把它里面积累的 if 拆干净。动第一行之前,你并不知道哪些行为是业务要的、哪些是历史 bug 变成的约定。这时候让 Cursor 直接改,最快的结局是:本地跑通了,上线后退款金额差了 3 分钱,客服一周接两百通电话。
一句话结论
改遗留代码的顺序是先固化现在的行为,再改。先写 5 条特性化用例(characterization test)把当前输出钉死——不管它对不对,改完再对比行为有没有意外变化。5 条不是随便选,按「黄金路径、边界、异常、并发、脏数据」各一条。实测一个 1,200 行、38 个函数、被 7 处调用的订单模块,改完出现 3 处行为变化,2 处是有意的、1 处是意外(金额精度从分变成厘),靠用例当场发现。
这篇解决什么问题
三种情况最常见。一是不敢改:代码能跑,但没人敢动,因为说不清某个判断条件是业务规则还是当年打补丁留下的。二是改完不知道坏没坏:本地跑通主流程就上线,坏的是边缘分支,两周后才被发现。三是用例按「应该」写:跑起来全红——老代码本来就不按应该的算,你花三天修测试,最后放弃。
根子是把测试当成「验证正确性」,而在遗留代码里它首先得是「记录现状」。特性化用例的目标不是绿,是把当前行为写成断言——哪怕这个行为看起来是错的,也照原样钉住。有了基线,你才能区分「我改坏了」和「它本来就那样」。
开始前要准备什么
- 能跑起来的本地环境,包括一份脱敏的真实数据。造的数据跑不出边界行为,那些边界恰恰是老代码里 if 最多的地方。
- 一段可回退的版本控制状态。改之前先打 tag,别依赖记忆回滚。
- 一张调用关系图。先让 Cursor 输出「这个模块被哪些文件调用、每个调用方传什么参数」,否则你不知道改一个函数会波及谁。
- 一条纪律:跑出红色断言时,改断言,不改代码。这条在第一步必须守住,否则整件事会退化成修测试。
具体怎么做
第一步:先摸清波及面,再挑 5 条路径
波及面决定用例挑哪里。让 Cursor 输出三样:被谁调用、每个调用方的入参形态、函数之间的依赖顺序。实测这个订单模块被 7 个文件调用,其中 3 个传的是完整订单对象、4 个只传 ID;有两个函数只在退款链路里出现,改它们不会影响下单。摸清这些,用例才不会全挤在主流程上。
这是订单模块的代码(1,200 行,38 个函数,附后)。我要重构它,但必须先知道改动会波及哪里。
请只做静态分析,不要改代码,输出三份:
1. 调用关系:这个模块里的函数,被哪些文件、哪些函数调用。标出只被内部调用和会被外部调用的(后者改动风险高);
2. 入参形态:每个外部调用点传进来的参数长什么样(完整对象 / 只有 ID / 部分字段);
3. 依赖链:如果我要改 calcAmount 和 applyDiscount 这两个函数,会连带影响哪些对外行为,按影响面从大到小排序。最后给建议:基于以上,哪 5 条路径最该被测试覆盖?按「黄金路径、边界、异常、并发、脏数据」各挑一条,并说明每条挑中的理由和它可能踩到的坑。
第二步:写 5 条特性化用例,允许断言「错的行为」
特性化用例的写法与普通单测相反:先跑一次拿到实际输出,再把它写成断言,结构是「输入 → 记录实际输出 → 断言等于实际输出」。实际输出明显是 bug 的(如负数金额)照样断言,但在用例名标 `known-bug`,改完再决定修不修。
5 条的分工要写死:黄金路径验主流程、边界验临界值(金额 0、数量 1、折扣 100%)、异常验非法输入、并发验库存扣减、脏数据验历史遗留的空字段。这五类覆盖了老代码里绝大多数分支。
这 5 条路径要写成特性化用例。注意:这是遗留代码,我的目标是记录当前行为,不是验证正确性。
写法要求:
1. 每条用例:构造输入 → 调用 → 断言等于「当前实际输出」;
2. 先告诉我当前实际输出是什么(可以让我先跑一遍贴给你,或你从代码推断并标注「推断值,需实测确认」);
3. 如果实际输出明显是 bug(如负数金额、空值当 0 处理),照样写成断言,但在用例名上加 known-bug 标记,并单独列一份「疑似 bug 清单」;
4. 用例不要依赖外部服务,数据库和时间都要能 mock;
5. 每条用例加一行注释:它守的是什么行为,改动时如果这条红了意味着什么。输出:5 个完整的测试函数 + 一份疑似 bug 清单。
第三步:跑基线,全绿才算开始
5 条用例跑完必须全绿。如果有一条红,说明你写的断言是「应该的行为」而不是「实际的行为」,回去改断言。这一步是全流程的闸门——基线不绿就动手改,等于没有基线。
跑绿之后做两件事:打 tag、把 5 条用例的实测输出抄成一份基线快照(一个文本文件,逐条列出输入与输出)。后面比对时,快照比测试报告更好读,因为它能一眼看出「第 3 条的输出从 1,280.00 变成了 1,280.000」。
5 条用例已全部跑通。请帮我做基线快照:
1. 逐条列出:用例名 / 输入摘要 / 实际输出(金额精确到小数点后两位并注明单位)/ 关键中间变量的值;
2. 生成一个文件 baseline.txt 的内容,格式要便于后续 diff(一条用例一块,块内一行一个值);
3. 标出哪些输出看起来可疑(数值异常、精度异常、null 被当 0),但不要修改它们。这份快照是后面判断「行为有没有意外变化」的唯一依据,所以宁可多记几个中间值。
第四步:动手改,然后用 diff 逐条解释变化
改的时候守一条:一次只改一个关注点。拆 if、改命名、抽函数分三次做,每次都跑 5 条用例。混在一起改,红了也说不清是哪一步导致的。
改完的最后一步最关键:把新输出与基线快照 diff,逐处给解释。差异分三类——有意、顺带(改 A 导致 B 也变)、意外(不该变却变了)。实测 3 处差异里 2 处有意、1 处是意外的精度变化:金额从两位变三位,因为新代码换了除法顺序。
这是我改完后的 5 条用例输出,以及改动前的基线快照。请帮我做差异分析。
逐条比对,对每一处数值或行为的差异输出:
1. 差异位置(哪条用例、哪个值);
2. 差异类型:有意(本次改动目标)/ 顺带(连带影响,需判断是否接受)/ 意外(不该变却变了);
3. 影响面:这个差异在业务上意味着什么,会不会影响对账、退款、开票;
4. 建议:接受、回退这部分改动、还是单独提一个修复。最后回答两个问题:
– 有没有哪条用例在改完后「不再守原来的行为了」,即断言虽然过了但守的东西变了?
– 这次改动中,哪一处最容易在线上以低频方式暴露(比如只在特定折扣组合下出现)?
实测记录
素材是一个 1,200 行的订单金额模块,38 个函数,被 7 个文件调用。读代码加摸波及面 25 分钟,Cursor 输出的调用关系漏标两处(动态方法名调用、队列消费者),靠人工搜函数名补上,所以静态分析结果要用搜索交叉验证。
写 5 条用例 50 分钟,是全程最费时的。慢在两处:一是 mock 时间,老代码里直接调了当前时间函数,要在三种边界下复现就得能控制时钟;二是脏数据那条,真实库里有一条订单的折扣字段是空字符串而不是 null,老代码靠隐式转换当 0 处理,这个行为必须先钉住。
跑基线 10 分钟,第一轮 2 条红,都是因为断言写成了「应该的行为」(折扣按四舍五入、退款按订单原价),改成记录实际值后全绿。打 tag 加快照 5 分钟。
改代码 40 分钟,分三次做:先抽函数(不改逻辑)、再拆 if、最后改命名,每次跑一遍用例。比对 20 分钟,3 处差异:折扣计算的中间值从分改厘(有意)、退款顺序调整导致日志顺序变化(顺带,接受)、金额精度从两位变三位(意外,回退)。全程约 2 小时 30 分钟。
最容易错的 3 个地方
第一,用例按「应该」写而不是按「实际」写。这是遗留代码里最常见的翻车方式:写出来的测试全红,你花几天修测试,修到最后发现自己在改一个不敢改的模块,于是放弃。规则要反过来:红了改断言,不改代码。
第二,5 条用例全挑主流程。主流程本来就不会坏,老代码的坑都在边缘分支——数量为 1 时、折扣刚好 100% 时、字段是空字符串时。5 条必须按五类各一条,不能因为「主流程最重要」就写三条。
第三,改完只看测试是否变绿,不看 diff。变绿只说明「没有回归到断言失效」,说明不了「行为没变」。精度从两位变三位、日志顺序变化这类差异,测试不会红,但对账会炸。所以最后一定要做输出 diff。
可复用的东西
5 条路径分工:黄金路径、边界(0 / 1 / 100%)、异常(非法输入)、并发(库存扣减)、脏数据(空字符串与 null)。
特性化用例写法:输入 → 记录实际输出 → 断言等于实际输出。疑似 bug 标 known-bug,照样断言。
基线快照:一条用例一块,逐行记输入与中间值,专为 diff 设计。比测试报告好读。
一次一个关注点:抽函数、拆 if、改命名分三次,每次跑全套用例。
差异三分类:有意、顺带、意外。意外的必须回退或单独立项,不能默认接受。
常见问题解答
为什么用例要按「实际行为」写而不是「应该的行为」
因为老代码本来就不按应该的算,按应该写出来的测试全红,你会花几天修测试,修到最后发现自己在改一个不敢改的模块然后放弃。这次第一轮就有 2 条红,都是断言写成了「折扣四舍五入」「退款按订单原价」,改成记录实际值后全绿。规则要写死:红了改断言,不改代码。
5 条用例怎么挑才够用
按五类各一条:黄金路径验主流程、边界验临界值(金额 0、数量 1、折扣 100%)、异常验非法输入、并发验库存扣减、脏数据验历史遗留的空字段。不能因为主流程最重要就写三条,主流程本来就不会坏,老代码的坑都在边缘分支。这个模块被 7 个文件调用,其中两个函数只在退款链路出现,改它们不影响下单,摸清波及面才知道用例该往哪放。
改完测试全绿就算完成了吗
不算。变绿只说明没有回归到断言失效,说明不了行为没变。这次改完 3 处差异里,金额精度从两位变三位(新代码换了一种除法顺序)是测试抓不到的,但它会让对账炸。所以最后一定要把新输出和基线快照做 diff,差异分有意、顺带、意外三类,意外的不许默认接受。
Cursor 的静态分析能直接信吗
要交叉验证。这次它输出的调用关系漏了两处:一个是通过动态方法名调用的、一个在队列消费者里。两处都是靠人工搜函数名补上的。所以拿到调用关系图后,用函数名全文搜索复核一遍,尤其留意字符串拼接出来的方法名和消息队列的消费入口。
本网站的部分内容可能来源于网络或用户投稿,仅供大家学习与参考,如有侵权,请联系客服: hhxmw778 进行删除处理。
本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
教程如引导您添加微信或有另外收费服务,请自行甄别!
如遇充值环节或绑定支付账户或输入支付密码之类的异常步骤,建议停止操作!本站不对操作项目的损失负责!
本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现任何资源有问题请向站长举报下架
(手机微信用户建议用默认浏览器打开,可体验完整功能)
