
关于
追踪每个面向用户的按钮/触点的完整状态变化序列,查找功能单独正常但相互抵消、产生错误最终状态或使 UI 处于不一致状态的 Bug。
name: click-path-audit description: "追踪每个面向用户的按钮/触点的完整状态变化序列,以发现功能单独正常但相互抵消、产生错误最终状态或使 UI 处于不一致状态的 bug。使用场景:系统性调试未发现 bug 但用户报告按钮失效,或在任何涉及共享状态存储的重大重构之后。" origin: community
/click-path-audit — 行为流审计
发现静态代码阅读遗漏的 bug:状态交互副作用、顺序调用之间的竞态条件,以及静默撤销彼此操作的处理器。
此技能解决的问题
传统调试检查:
- 函数是否存在?(缺失连接)
- 是否崩溃?(运行时错误)
- 是否返回正确类型?(数据流)
但它不检查:
- 最终 UI 状态是否与按钮标签承诺的一致?
- 函数 B 是否静默撤销了函数 A 刚做的操作?
- 共享状态(Zustand/Redux/context)是否有副作用取消了预期操作?
真实案例:一个"新邮件"按钮调用了 setComposeMode(true) 然后 selectThread(null)。两者单独都能正常工作。但 selectThread 有一个副作用会重置 composeMode: false。按钮什么都没做。系统性调试发现了 54 个 bug——但这个被遗漏了。
工作原理
对目标区域中的每个交互触点:
1. 识别处理器(onClick、onSubmit、onChange 等)
2. 按顺序追踪处理器中的每个函数调用
3. 对每个函数调用:
a. 它读取了什么状态?
b. 它写入了什么状态?
c. 它对共享状态有副作用吗?
d. 它是否作为副作用重置/清除了某些状态?
4. 检查:后续调用是否撤销了前面调用的状态变更?
5. 检查:最终状态是否是用户从按钮标签期望的结果?
6. 检查:是否存在竞态条件(异步调用以错误顺序解析)?
执行步骤
步骤 1:映射状态存储
在审计任何触点之前,为每个状态存储操作构建副作用映射:
对范围内的每个 Zustand store / React context:
对每个 action/setter:
- 它设置了哪些字段?
- 它是否作为副作用重置了其他字段?
- 记录:actionName → {sets: [...], resets: [...]}
这是关键参考。如果不知道 selectThread 会重置 composeMode,"新邮件"bug 是不可见的。
输出格式:
STORE: emailStore
setComposeMode(bool) → sets: {composeMode}
selectThread(thread|null) → sets: {selectedThread, selectedThreadId, messages, drafts, selectedDraft, summary} RESETS: {composeMode: false, composeData: null, redraftOpen: false}
setDraftGenerating(bool) → sets: {draftGenerating}
...
DANGEROUS RESETS(清除非自身状态的操作):
selectThread → resets composeMode(属于 setComposeMode)
reset → resets everything
步骤 2:审计每个触点
对目标区域中的每个按钮/开关/表单提交:
TOUCHPOINT: [按钮标签] in [Component:line]
HANDLER: onClick → {
call 1: functionA() → sets {X: true}
call 2: functionB() → sets {Y: null} RESETS {X: false} ← 冲突
}
EXPECTED: 用户看到 [按钮标签承诺的描述]
ACTUAL: X 为 false 因为 functionB 重置了它
VERDICT: BUG — [描述]
检查以下每种 bug 模式:
模式 1:顺序撤销
handler() {
setState_A(true) // sets X = true
setState_B(null) // 副作用:resets X = false
}
// 结果:X 为 false。第一次调用毫无意义。
模式 2:异步竞态
handler() {
fetchA().then(() => setState({ loading: false }))
fetchB().then(() => setState({ loading: true }))
}
// 结果:最终 loading 状态取决于哪个先解析
模式 3:过期闭包
const [count, setCount] = useState(0)
const handler = useCallback(() => {
setCount(count + 1) // 捕获了过期的 count
setCount(count + 1) // 同样的过期 count——增加 1 而不是 2
}, [count])
模式 4:缺失状态转换
// 按钮显示"保存"但处理器只验证,从未实际保存
// 按钮显示"删除"但处理器设置标志而未调用 API
// 按钮显示"发送"但 API 端点已被移除/损坏
模式 5:条件死路径
handler() {
if (someState) { // someState 在此时始终为 false
doTheActualThing() // 永远不会到达
}
}
模式 6:useEffect 干扰
// 按钮设置 stateX = true
// 一个 useEffect 监听 stateX 并将其重置为 false
// 用户看到什么都没发生
步骤 3:报告
对发现的每个 bug:
CLICK-PATH-NNN: [严重程度: CRITICAL/HIGH/MEDIUM/LOW]
触点: [按钮标签] in [file:line]
模式: [顺序撤销 / 异步竞态 / 过期闭包 / 缺失转换 / 死路径 / useEffect 干扰]
处理器: [函数名或内联]
追踪:
1. [调用] → sets {field: value}
2. [调用] → sets {field: value} RESETS {other: value} ← 冲突
预期: [用户期望发生什么]
实际: [实际发生了什么]
修复: [建议的修复方案]
兼容工具
Claude CodeCursor
标签
前端开发
