「这一改会破坏什么」——改完代码、准备提交前的三问体检。 不是通用代码审查,只查一类故障:**两个必须触发相反动作的状态,在表示上变成了一模一样**。 三问:①这个默认值会不会把「我不知道」变成一句听起来很肯定的话? ②我这条判据查的是含义,还是恰好长这样?③这条检查坏了的时候,是红的还是绿的? 触发方式:/what-does-this-break,或用户说「这一改会破坏什么」「体检一下」「提交前过一遍」。 主动触发:本次改动里新增了默认值/兜底分支、新增或改动了判据(正则、阈值、字符串比较)、 新增了检查或断言 —— 三者任一出现且准备提交时,用一句话建议跑一次。
Install
npx skillscat add 843773296-rgb/lanxiu-yunshang-agent/what-does-this-break Install via the SkillsCat registry.
这一改会破坏什么
它只查一件事
两个必须触发相反动作的状态,在表示上一模一样。
这类故障不报错、不崩溃、看起来一切正常,所以测试和评审都抓不到。真实例子:
「算不出」和「0」、「下过单」和「已经卖掉了」、「有闸」和「会拦的闸」、
「数据对」和「产生数据的路径对」、「删掉了能力」和「忘了实现」、
「环境问题」和「数据问题」、「碰巧对上」和「真的对上」、「噪音」和「回归」。
别的问题(性能、命名、重复代码)不归这个 skill 管,不要顺手评论。
铁律
- 先读真 diff,不许凭记忆回答。 开头必须跑
git diff HEAD(已暂存就git diff --cached),
没读到的改动不许下结论。 - 「没问题」不是答案。 每一问要么给出具体的
文件:行号,要么给出一句
「本次改动没有这类东西,因为……」并说明理由。光写「已确认无风险」判为没做。 - 三问都要答。 答不出来说明这处改动自己还没想清楚 —— 那就是结论,照实说,不要糊过去。
- 只报,不改。 发现问题列出来交给对方决定;除非用户说「顺手改了」。
第一问:这个默认值会不会把「我不知道」变成一句听起来很肯定的话?
在 diff 里怎么找候选(grep 新增行):
or 0 || 0 ?? 0 .get(..., 0) default= fillna( COALESCE(
except.*: *return catch *{ *return if not .*: .*= ?: '' |default("")还要看:新加的返回值里有没有「算不出的时候返回什么」这一支。
判什么:这个值落到最终结果里的时候,看结果的人能不能分辨「真的是这个值」和「没算出来」。
分辨不了就是这一问要抓的东西。
合格的回答长这样:
stock.py:88—— 没有销售记录时可售天数返回0。结果表里「0 天」是最高优先级告警,
而「从没卖过」根本不该排进去。两者在表里一模一样。→ 改成返回None并在口径里写明「算不出就说算不出」。
这个项目栽过的真事:
- 可售天数算不出时写 0 → 「今天就断货」和「这个 SKU 没数据」在告警列表里长得一样。
- 模型编出来的可售天数有单位、有小数点、排得进表格 —— 默认「像样」是大模型的默认行为,
所以这一问在有大模型参与的地方权重最高。
第二问:我这条判据查的是含义,还是恰好长这样?
在 diff 里怎么找候选:新增或改动的正则、字符串比较(in / == / startswith / includes)、
阈值数字、检查名、提示词里的规则条款。
判什么:必须当场写出两个例子——
| 要写出来的 | 写不出来说明 |
|---|---|
| 一个含义对、但长得不一样的输入 | 判据会漏,你不知道漏在哪 |
| 一个长得一样、但含义相反的输入 | 判据会误拦,而一道会误拦的闸迟早被人关掉 |
两个例子都举不出来 = 这条判据还没想清楚。误报比漏报贵:一条误报会让人去改一个本来正确的东西。
合格的回答长这样:
prompts.py:TL29—— 「不许给可售天数」。
漏:换个说法写成「大约还能撑两周」,字面没有「可售天数」,判据看不见。
误拦:标准答案里那句「没卖过,给不出可售天数」也含这四个字,会被判违规 —— 实测真的判错了。
→ 收窄成「天数出现在一句同时说着没卖过的话里」。
这个项目栽过的真事(同一个错栽了至少五次):
- 判据贴着字面:「不许写成 0 天」把真的 0 天判成违规。
- 把规矩挂在会过期的前提下:「数据量不够时不许用件数阈值」—— 数据一多,规矩自己失效。
→ 不带前提,拆成独立条款。 is比小整数分不开「抄了一份 2」和「取了一份 2」(小整数是同一个对象)→ 改成查源码。
第三问:这条检查坏了的时候,是红的还是绿的?
在 diff 里怎么找候选:新增的 assert、检查函数、门禁步骤、自测、CI 步骤、hook。
判什么:做一次咬合测试(mutation test)——
- 故意把被检查的对象改坏(不是改检查本身);
- 确认这条检查真的变红;
- 改回来。
红不了 = 这条检查等于没装,而且它每次都报绿,比没有检查更糟(它在给人虚假的安全感)。
同时查三种「坏了是绿的」的写法:
- 检查名用 f-string / 模板字符串拼 → 咬合记录按名字对不上,永远找不到该红的那条。检查名写死。
- 退出码被吞 →
门禁 | tail && 提交、门禁; 提交。
(这一条已有 hook 自动拦:~/.claude/hooks/commit-gate-exitcode.mjs,被拦下就照它给的写法改。) - 入口条件没满足,整段没跑却报成功 → 循环没进、
if __name__那类自识别判断比不上、
提前return了。查法:让它打印一行「实际跑了 N 步」,和期望的 N 对账。
这个项目栽过的真事:
check.sh红着,因为./check.sh | tail -2 && git commit,管道的退出码来自tail。犯了两次。- hook 的自测用
import.meta.url === \file://${process.argv[1]}`` 判断是否被直接运行,
相对路径调用下比不上,整个 main 块没跑,退出码 0。一个跑了等于没跑还报成功的自测。 - 统计脚本用
dict(findall(...))按符号做键,31 条压成 2 条,不报错。→ 加总数对账才发现。 - 副本测试没软链
.venv,7 项「失败」全是环境问题,和真的数据问题混在一张报告里。
输出格式
先一行结论,再一张表,再把要动的地方列出来。不写长篇分析。
读了 N 个文件 / M 处改动(git diff HEAD)
| 问 | 结论 | 证据 |
|---|---|---|
| ①默认值把「不知道」说成肯定话 | ⚠️ 1 处 | stock.py:88 算不出返回 0,和真的 0 天在告警表里一样 |
| ②判据贴字面还是贴含义 | ✅ 过 | 只改了 channel.py:12 的注释文案,没动判据 |
| ③检查坏了是红是绿 | ❌ 绿的 | muslin_check.py:40 新增 3 项,未做咬合测试 |
要动的:
1. stock.py:88 改返回 None(或说明为什么 0 在这里是安全的)
2. muslin_check.py 那 3 项各故意改坏一次,确认会红三问全过就一行「三问全过」加证据,不要为了显得认真而编问题出来 —— 编出来的问题也是一种误报。