OP Stack 删除式评审方法论:如何证明被删除的外部名字与状态写入没有留下残骸
【免费下载链接】optimismOptimism is Ethereum, scaled.项目地址: https://gitcode.com/GitHub_Trending/op/optimism
本篇基于 OP Stack monorepo 的删除式代码评审规范(deletion-reviewer代理定义及其指定的方法文档)展开,讲解当一次 diff 删除公开符号、wire 字段、指标、配置键或状态写入时,如何系统性地证明"没有任何引用者被静默降级、也没有任何存留状态失去覆盖"。读完本文,你能掌握该 monorepo 中删除类 PR 的完整评审流程:全树引用清扫、存活写入者触发窗口分析、连带清理清单、依赖工作区级联检查,以及评审输出的标准格式。
1. 删除式评审解决什么问题
删除与新增的失败模式不同。删除一个东西时,编译器已经证明了没有任何代码还需要它——一旦编译通过,泛型代码评审("看看有没有漏改")就到此为止了。但删除真正会咬人的是两类编译器永远看不见的问题,这正是 docs/ai/deletion-review.md 所定义的两大检查目标:
- 代码之外的引用仍然存活:公开文档、Grafana 仪表盘、CI 配置、脚本里以字符串形式引用的名字,删掉后不会报编译错误,只会静默地失效;
- 被删除代码曾是某份状态的唯一/部分写入者:删掉后其余写入者"存在"不等于"覆盖",某些时间窗口内该状态会悄悄变陈旧。
2. 何时触发删除式评审
根据 docs/ai/deletion-review.md,当 diff 删除以下任意一类内容时,本评审适用:
- 公开符号:类型、函数、事件、枚举变体、接口方法、参数;
- wire 名字:RPC 方法名、JSON 响应字段、WS 订阅;
- 指标名或指标标签值(label value)——注意,不是指标名也常常不是问题,单个 label 值(如
label="local-safe")被删同样会击穿依赖它的查询; - 配置键、CLI flag、环境变量;
- 测试或子测试名称。
3. deletion-reviewer 代理:使命、方法与边界
方法由 .claude/agents/deletion-reviewer.md 中定义的deletion-reviewer代理执行。其 frontmatter 声明如下:
name: deletion-reviewer;model: opus(使用最强推理模型执行该评审);description:评审删除东西的 diff——公开符号、wire/RPC 字段、指标名或 label 值、事件、配置键、CLI flag。抓住泛型评审漏掉的两种失败模式:代码之外仍然存活的引用(文档、仪表盘、示例、CI 配置),以及"剩余写入者未必在删除者覆盖的每个窗口内触发"的状态写入。用于在提交任何移除外部可观察名字或向存留状态写数据的 PR 之前。
该代理文件本身刻意保持精简,其核心约定是:方法唯一来源是 docs/ai/deletion-review.md——"每次先读它,并遵循它;它是 single source of truth,本文件永不覆盖它"。在没有该代理支持的 harness 下,开发者也可以直接按方法文档手工执行评审。代理在大纲层面执行四个动作:建立删除清单(代码形式 + 字符串形式)、执行全树引用清扫及其三分类、执行删除写入分析("存活者何时触发,而不是它们是否存在")、应用连带清理与误报陷阱规避。
根目录 AGENTS.md 也将其索引为官方评审指南之一,说明它是仓库 AI 工程流程的一部分(与.claude/agents/下的 go-code-reviewer、rust-code-reviewer、ci-config-reviewer 等语言型评审代理并列)。
4. 检查一:引用清扫必须越过代码边界
方法是:先把删除物建成删除清单(deletion inventory),同时记录它的代码形式和字符串形式(JSON 键、指标 label 值、方法名、子测试名),然后在整棵树上清扫。编译器看不见的引用点,按"被遗漏的频率"从高到低排列:
- 公开文档(
docs/public-docs/):字段列表和示例 payload——一个 JSON 示例会在代码围栏里把同一个 wire 字段再嵌入第二次,只查字段表会漏掉它; - Grafana 仪表盘与监控配置(
**/grafana/**/*.json):删除一个指标或 label 值,会让某个 panel 永久绘制一条空序列。当仪表盘存在成对副本时,必须保持逐字节一致。 仓库中正好有这样的成对副本:kona-node 仪表盘 与 kona-node-dev 仪表盘,经diff验证两者当前逐字节相同。且该仪表盘的 PromQL 大量依赖 label 值,例如kona_node_block_labels{label=~"local-safe"}——如果某天 PR 删除了local-safe这个 label 的写入,18 个 panel 里引用它的查询就会静默变成空序列,而所有编译与 CI 检查都会保持绿色。这类指标的写入者可在 rust/kona/crates/node/engine/src/metrics/mod.rs 中找到,这正是检查二要追踪的对象; - CI 配置、justfile、workflows:测试名、二进制名、包列表——都是被枚举的字符串,名字一改就会静默跳过或炸掉;
- README、compose 文件、脚本。
每个清扫命中必须精确归类为三者之一:
- must-update(必须更新):在同一 PR 内修掉;
- deliberate survivor(有意存留):例如另一个服务仍在填充的共享 Go 类型、为 wire 兼容性保留的枚举值——在 PR 描述中写明理由;
- 同名不同概念(same-name, different concept):名字是被重载的,同一个词可能在一个子系统标记链头、在另一个子系统表示逐消息验证阈值。清扫不得越界去动共享名字的存活功能;边界微妙时,把该边界记录进 PR 描述。
5. 检查二:删除写入——证明"存活者何时触发"
这是最隐蔽的删除缺陷形态:被删代码是某份存留状态(状态字段、tracker、指标、head label)的多个写入者之一,而评审通过"观察到其他写入者存在"来确认安全。存在不等于覆盖(Existence is not coverage)。
对每一处被删除的写入,方法文档要求三步走:
- 枚举同一状态的所有存活写入者;
- 对每个存活写入者,确立其精确的触发条件——触发事件、守卫条件、运行模式;
- 证明存活者触发条件的并集覆盖被删写入者覆盖的全部窗口。
容易被漏掉的窗口有五类:启动/初始化、同步模式(EL/snap sync、derivation 尚未运行、forkchoice 更新被门控的阶段)、重置(resets)、reorg(需要把值向后移动的写入者)、错误/停止路径(error/halt paths)。
一个未被覆盖的窗口意味着:恰恰在运维人员或下游服务(健康监控、仪表盘)盯着该值的时候,它悄悄变陈旧。如果旧耦合是偶然形成的,应当把新耦合显式化,而不是把被删路径恢复回来。
方法文档还特别强调测试要求:修复的测试必须钉住语义而不仅是快乐路径。如果该写入必须能把值向后移动,就要断言这一点——否则日后某次"只允许前进"的加固会重新引入陈旧性。
6. 连带清理清单(Consequential cleanups)
删除之后,同一 PR 内应当完成的五类连带清理:
- 如今无法产生的代码:唯一生产者已被删除的错误变体或分支,随生产者一起删掉;
- 死参数:穿过接口传递、但删除后无人读取的值——同一 PR 内收缩签名;
- 孤儿化副本:被删代码可能是某个上游类型/辅助函数本地副本存在的唯一理由(重新声明的错误类型、拷贝来的 parser)。对被删名字做符号 grep 是发现不了这些的;要问"被删代码证明了什么",而不只是"它引用了什么";
- 空洞测试(vacuous tests):对已删字段的断言可能变成零值比零值而永远通过。优先把被删概念变成显式错误,而不是返回零值。当被删断言被替换时,要证明替代者能够失败——临时反转它守护的性质(例如给宽松解析契约临时加
deny_unknown_fields)并看着它变红。一个构造上不可能失败的测试(断言类型系统已保证的东西)保护不了任何东西。仓库中 rust/kona/crates/protocol/genesis/src/chain/config.rs 就是一个正面示范:ChainConfig上带deny_unknown_fields属性,并配有专门的测试用例守护该属性,断言一个只多一个键的"合法"配置必须被拒绝; - wire 兼容性:被删的 RPC/JSON 字段在宽松客户端中解析为零值——追踪仓库内每个消费者对该零值的处理,并对仓库外读者披露该删除(破坏性变更标记 + 迁移说明)。
7. 依赖与工作区级联(Dependency and workspace fallout)
删除会以编译器和 clippy 都不会报警的方式波及构建元数据,以下四项全部保持"全绿":
- 孤儿化依赖:删除文件或模块可能抽走某个 crate 对某依赖的最后一处使用。要把被删文件 import 过的每一个符号都 grep 一遍——不允许"肯定还在用"的捷径——然后本地跑未用依赖门禁。该方法文档给出的命令是
cargo +nightly udeps --release --workspace --all-features --all-targets,即 CI 命令;与仓库一致:rust/justfile 中的check-udepsrecipe 正是cargo +{{NIGHTLY}} udeps --release --workspace --all-features --all-targets。这是唯一能抓住这一类问题的检查; - feature 转发移除:被删依赖可能在 crate 的
[features]列表里带有转发项("dep/feature"形式的转发),要逐个确认下游消费者自己启用了被转发的 feature——那个"静默依赖传递性启用"的消费者就是发现项; - 过期的 feature 列表字符串:启用代码被删掉的
"dep/feature"条目能逃过符号 grep(它引用的是依赖名而非符号),且没有任何工具会报警。要显式清扫[features]段落中出现的被删 crate 名与 feature 名; - 独立工作区的 lockfile:通过 path-dependency 依赖被改 crate 的工作区(例如 SP1 guest programs 工作区)是独立解析的。要重新生成其 lockfile——CI 以新鲜度为门禁(
just lock-sp1-guest/just check-sp1-guest-lock)——并且要在该工作区自己的解析下(--manifest-path)编译受影响的消费者,而不是根工作区下:一个依赖了已消失的传递性 feature 启用的 crate 只会在独立解析下失败。仓库中这一机制真实存在:rust/kona/sp1/programs/Cargo.toml 是一个独立 Cargo 工作区(members 为super-range与super-aggregation),其注释明确说明"该工作区必须保持分离以隔离 SP1 加密补丁";rust/justfile 的check-sp1-guest-lock用cargo metadata --manifest-path ... --locked校验 guest 工作区 Cargo.lock 与新依赖同步,过期时给出指向just lock-sp1-guest的修复提示(lock-sp1-guest recipe)。
此外,仓库中存在大量[package.metadata.cargo-udeps.ignore]条目,本身就印证了这类检查的实战价值:例如 rust/op-reth/crates/chainspec/Cargo.toml 用注释解释了为何某个 self dev-dependency 需要被 udeps 忽略(它激活 feature 而非 import 符号)。
8. 误报陷阱(False-positive traps)
评审时最容易被误报的三种情形,方法文档明确列出:
- 共享类型:从某个实现输出中删除的字段,可能合理地保留在另一个服务仍在填充的共享结构体里。把它也从共享结构体里删掉是另一项范围更宽的变更——不要把存留者标记为"漏网残骸";
- 重载名字:即"同名不同概念";一次 grep 命中是一个问题,不是一个发现;
- 重复产物:成对的仪表盘、镜像配置必须一起更新;只标记其中一个文件的发现是不完整的。
9. 评审输出格式与职责边界
deletion-reviewer代理的输出被严格规定为四个小节(代理文件原文结构):
- Summary:一两句话——删了什么、删除是否完整且安全;
- Critical Issues:未被覆盖的写入窗口,以及会改变行为的 must-update 引用;没有就写空——明确说明没有;
- Findings:按 High / Medium / Low 排序。每项包含What(带
file:line)、Why它会咬人、How修(具体方案); - Verified clean:列出所有扫干净的清扫与写入分析及其证据(grep 了什么、追踪了哪些写入者)——"缺席声明"正是这种评审的价值所在,所以要展示其依据。
职责边界(Boundaries)三条:
- 范围是删除及其爆炸半径,不是一般代码质量——那是语言型评审代理(go-code-reviewer / rust-code-reviewer)的职责;
- 不修改任何文件,只报告;
- 如实报告:列出清扫过什么、以及未能检查什么。
10. 小结
OP Stack monorepo 对"删除"建立了独立于语言评审的专项方法论:用代码形式 + 字符串形式双清单做全树引用清扫并三分类,用"触发条件并集"而非"写入者存在性"来验证删除写入的覆盖性,再用连带清理、构建元数据级联与误报陷阱三张清单收尾。配合check-udeps、SP1 guest 独立工作区 lockfile 门禁等真实 CI 设施,这套流程保证删除类 PR 在合并前就消除了文档残留、仪表盘空序列、陈旧状态与孤儿依赖四类静默回归。
【免费下载链接】optimismOptimism is Ethereum, scaled.项目地址: https://gitcode.com/GitHub_Trending/op/optimism
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考