happy-nil-empty-error:代码Review的四条路径

happy-nil-empty-error:代码Review的四条路径

Review 最常见的失败,是把“看过了”误认为“审过了”。

看过 diff,不等于审过行为。总结改动,不等于发现风险。提了几个风格建议,不等于覆盖边界。说“整体没问题”,不等于系统真的安全。

好的 Review 不是礼貌地复述改动,而是主动寻找行为错误。

我现在最常用的最低基线,是四条路径:

1
2
3
4
happy
nil
empty
error

这四个词很朴素,但能抓住大量真实问题。

一、Happy:正常世界是否成立

Happy path 检查正常输入、正常权限、正常依赖下,主流程是否成立。

它要问:

  • 正常用户能不能完成操作?
  • 主流程是否返回正确结果?
  • 新旧调用方是否兼容?
  • 用户可见行为是否符合需求?
  • 成功路径是否有测试或人工验证?

Happy path 是最容易被测到的路径,也是最容易让人过早放心的路径。

一个导出功能正常生成文件,一个设置页正常保存选项,一个列表正常展示数据,这都只是说明系统在正常世界里能工作。

但真实世界不总是正常。

所以 happy path 是起点,不是终点。

二、Nil:缺失时会不会炸

Nil path 检查缺失。

缺用户、缺 token、缺配置、缺依赖、缺字段、缺缓存、缺 navigator、缺文件。很多线上问题,不是因为逻辑多复杂,而是因为你以为一定存在的东西不存在。

Nil path 要问:

  • nullable 字段是否处理?
  • 缺用户、缺 token、缺配置时会怎样?
  • 是否存在直接解包、空指针、未判空访问?
  • 缺依赖时是 fail fast,还是悄悄产生半成功状态?
  • nil 和 empty 有没有混用?

举个例子。

一个 iOS 设置页在正常情况下能保存 preset。但如果 navigator 没绑定,点击设置会不会崩?如果 UserDefaults 里没有这个 key,是回到默认值,还是显示一个错误高亮?

这就是 nil path。

它经常抓到“本地能跑、线上炸”的问题。因为本地开发环境通常配置齐全、用户状态完整、fixture 数据漂亮。

三、Empty:空不是缺,也不一定是错

Empty path 检查“存在但没有内容”。

空字符串、空数组、空结果集、零条记录、空响应。

很多 bug 来自 nil 和 empty 被混在一起。

空列表不是缺列表。空字符串不是缺字段。零条记录不是查询失败。接口返回空数组,可能是一次成功的空结果,而不是异常。

Empty path 要问:

  • 空列表是否显示空态,而不是报错?
  • 空搜索结果是否是正常结果?
  • 空输入是否需要拒绝?
  • 空内容是否允许保存?
  • 空响应是否会触发无限 loading 或无限重试?

举个例子。

搜索页面请求成功,返回空数组。正确行为应该是显示“没有找到结果”,并给用户一个修改关键词的入口。错误行为是显示“加载失败”,或者一直转圈。

这类问题看起来小,但非常影响用户信任。

Empty path 的关键是语义。空不一定错,要看需求怎么定义。

四、Error:世界不配合时会怎样

Error path 检查失败。

网络超时、数据库失败、权限不足、磁盘满、第三方接口 500、JSON 解析失败、任务取消、并发冲突。

Error path 要问:

  • 错误是否被捕获?
  • 错误语义是否正确?
  • 用户是否看到合理反馈?
  • 日志是否可定位?
  • 指标是否可观察?
  • 是否需要重试?
  • 重试是否幂等?
  • 失败后状态是否可恢复?

很多系统不是在成功时分出高下,而是在失败时分出高下。

比如导出任务:文件上传成功了,但数据库状态写回失败。这不是简单的失败。它是半成功。如果系统从头重试,就可能重复上传文件。如果系统直接标记 failed,用户会看到失败,但实际上文件已经生成。

这种问题,happy path 永远抓不到。

你必须专门问 error path。

五、Review 不是建议越多越好

坏 Review 经常堆满细节建议:

  • 变量名可以更好。
  • 这里可以抽函数。
  • 这里可以换一种写法。
  • 这个注释不够优雅。

这些建议不一定错,但如果它们淹没了真正的行为风险,Review 就失败了。

Review 的优先级应该是:

  1. 阻断性行为错误。
  2. 可能导致数据、权限、稳定性问题的风险。
  3. 测试缺口。
  4. 契约和发布风险。
  5. 可维护性建议。

风格建议应该让路给行为问题。

六、Review 要贴着任务 Mode

同一段 diff,在不同 Mode 下 Review 重点不同。

如果任务是 Expansion,要问:

  • 新能力是否有完整状态流?
  • 错误态是否设计?
  • 发布和回滚是否考虑?
  • 新接口是否有契约测试?

如果任务是 Hold,要问:

  • 是否保持既有契约?
  • 是否只修当前问题?
  • 是否避免不必要重构?
  • 是否补了回归测试?

如果任务是 Reduction,要问:

  • 旧入口是否真正收口?
  • 删除是否有证据支持?
  • 兼容入口是否有清退说明?
  • 回滚是否可行?

没有 Mode 的 Review,很容易用错尺子。

七、一个好 Finding 应该长什么样

不要写:

1
建议增强错误处理。

这句话太软,不能行动。

改成:

1
2
3
4
5
[P1] 重试路径可能重复上传文件
Path: error
Trigger: 上传成功,但状态写回失败,重试从生成文件重新开始
Risk: 产生重复导出文件,用户状态不一致
Required validation: 从 uploaded_pending_persist 状态重试时,断言 renderer 和 uploader 不会再次调用

这才是有用的 Review。

它说明了路径、触发条件、风险和需要补的验证。

八、让 AI 做 Review 时要禁止它总结

AI 做 Review 时,最容易输出这种东西:

1
本次修改优化了导出逻辑,增加了错误处理,并补充了测试。整体结构清晰。

这不是 Review,这是摘要。

你应该明确要求:

1
2
3
4
请以代码审查姿态输出,优先寻找 bug、行为回归、边界遗漏和测试缺口。
不要先总结 diff。
按 happy、nil、empty、error 四条路径检查。
如果没有发现问题,也要列出剩余测试缺口。

AI 很适合做边界扫描,但前提是你要求它扫描边界,而不是让它“看看”。

九、四路径的真正价值

happy、nil、empty、error 并不是完整的软件质量模型。

但它们有一个巨大优点:简单、稳定、容易记。

每次 Review 前扫一遍:

1
2
3
4
正常路径成立吗?
缺失值会炸吗?
空结果语义对吗?
失败后状态安全吗?

很多问题就会浮出来。

这四条路径的价值,不在于它们高级,而在于它们能把 Review 从“我看了一遍”变成“我攻击了四个方向”。