不要在 API 命名中使用 check

本文讨论的是 check 命名在业界的默认语义约定,而不是个人风格偏好。[1]

在很多项目中,checkXxx 被滥用于 API、Service 甚至业务逻辑中,导致调用语义混乱。

check 的定位

check 是可以用的,但是它只能在一个非常窄的语义里使用。[2]
在大多数项目里, checkXxx() 是一个前置条件检查,不满足时抛出异常且没有业务副作用。[3]

  1. 返回类型 void不是 boolean
  2. 失败时抛出异常。
  3. 成功时什么都不发生。

例如:

1
2
3
private void checkStatus();
private void checkPermission();
private void checkConfig();

check 的滥用

对 check 的最严重最常见的滥用出现在命名 API 时。

命名 API 时,我们的目标是消除惊讶,而 check 在 API 语境下是语义不稳定的命名。[4] 对外的 API 一旦叫 checkXxxx,使用者就会在多种猜测中不知所措:[5]

  1. 成功时返回的是一个 boolean还是一个结果?
  2. 失败时是一个 false 异常 还是结果?
  3. 我调用这个 API 会不会修改一些状态?

举一个更具体的例子,当一个方法叫 checkOrder() 时:

如果想知道订单是否有效,我们会期待它叫 isValid()

如果想触发校验流程并拿到错误列表,我们会期待它叫 validateOrder()

如果想在订单无效时阻止程序运行,我们会期待它叫 ensureOrderValid()

check 就像一个黑色盲盒,不看实现永远不知道它会静默失败还是原地爆炸。 因此,它只适合作为类内部的辅助方法(Helper methods)。[6]

正确命名 API 的方式是这样的:

1
2
3
validateXxx()    // 有结果
verifyXxx() // 鉴权 / 第三方
ensureXxx() // 业务保障(失败抛异常)

总结

团队中可以将 check 约定为仅用于内部前置校验的方法命名,对外 API 必须使用结果或行为导向的动词。[7]

场景 用 check 吗
返回 boolean
返回 Result / Error
Controller / API
有业务副作用
失败直接抛异常
内部前置校验
private / protected
Domain 规则校验

  1. Guava Preconditions 使用 checkArgumentcheckStatecheckNotNull 表示前置条件检查;JDK Objects.checkIndex 也把 check 用于边界检查并在失败时抛异常。这说明 check 在主流库中常见的核心语义是“检查前置条件或边界”,不是任意业务动作:https://guava.dev/releases/33.4.8-jre/api/docs/com/google/common/base/Preconditions.htmlhttps://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Objects.html#checkIndex(int,int)↩︎

  2. Guava Preconditions 文档把 checkArgumentcheckStatecheckNotNull 分别对应参数、状态和非空前置条件;这些方法的失败语义是抛异常,支撑了“check 可用但语义很窄”的边界:https://guava.dev/releases/33.4.8-jre/api/docs/com/google/common/base/Preconditions.html↩︎

  3. Preconditions.checkArgument 在表达式为 false 时抛 IllegalArgumentExceptioncheckState 在状态条件不满足时抛 IllegalStateException,JDK Objects.checkIndex 在越界时抛 IndexOutOfBoundsException;这些例子都不是返回业务结果或推进业务流程的公开动作:https://guava.dev/releases/33.4.8-jre/api/docs/com/google/common/base/Preconditions.htmlhttps://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Objects.html#checkIndex(int,int)↩︎

  4. Swift API Design Guidelines 强调名字应在调用点清晰,因为声明只出现一次而使用会出现很多次;Microsoft .NET 成员命名指南也建议方法名使用动词或动词短语,布尔属性使用肯定式短语并可用 CanIsHas 增强语义:https://www.swift.org/documentation/api-design-guidelines/#naminghttps://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/names-of-type-members↩︎

  5. OpenAPI 3.1 规定 operationId 在 API 内应唯一且可被工具和库用于识别 operation;Google AIP-136 要求自定义方法用明确动词命名。公开 API 名字会进入文档、代码生成和客户端心智,因此 checkXxx 这类语义不稳定动词风险更高:https://spec.openapis.org/oas/v3.1.0.html#operation-objecthttps://google.aip.dev/136↩︎

  6. check 在 Guava / JDK 中常见于前置条件或边界检查工具方法;这支持把 checkXxx 保留在内部 helper 或模块内约定中,而不是泛化为公开业务 API 名称:https://guava.dev/releases/33.4.8-jre/api/docs/com/google/common/base/Preconditions.html↩︎

  7. Google AIP-136 要求自定义方法名使用动词,AIP-190 把命名视为跨资源、字段和方法的一致性契约;.NET 成员命名指南也要求方法使用动词或动词短语。这些指南共同支撑“对外 API 用结果或行为导向动词”的建议:https://google.aip.dev/136https://google.aip.dev/190https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/names-of-type-members↩︎