不要在 API 命名中使用 check
本文讨论的是 check 命名在业界的默认语义约定,而不是个人风格偏好。[1]
在很多项目中,checkXxx 被滥用于 API、Service 甚至业务逻辑中,导致调用语义混乱。
check 的定位
check 是可以用的,但是它只能在一个非常窄的语义里使用。[2]
在大多数项目里, checkXxx() 是一个前置条件检查,不满足时抛出异常且没有业务副作用。[3]
- 返回类型是
void而不是boolean。 - 失败时抛出异常。
- 成功时什么都不发生。
例如:
1 | private void checkStatus(); |
check 的滥用
对 check 的最严重最常见的滥用出现在命名 API 时。
命名 API 时,我们的目标是消除惊讶,而 check 在 API 语境下是语义不稳定的命名。[4] 对外的 API 一旦叫 checkXxxx,使用者就会在多种猜测中不知所措:[5]
- 成功时返回的是一个
boolean还是一个结果? - 失败时是一个
false异常 还是结果? - 我调用这个 API 会不会修改一些状态?
举一个更具体的例子,当一个方法叫 checkOrder() 时:
如果想知道订单是否有效,我们会期待它叫 isValid()。
如果想触发校验流程并拿到错误列表,我们会期待它叫 validateOrder()。
如果想在订单无效时阻止程序运行,我们会期待它叫 ensureOrderValid()。
check 就像一个黑色盲盒,不看实现永远不知道它会静默失败还是原地爆炸。 因此,它只适合作为类内部的辅助方法(Helper methods)。[6]
正确命名 API 的方式是这样的:
1 | validateXxx() // 有结果 |
总结
团队中可以将 check 约定为仅用于内部前置校验的方法命名,对外 API 必须使用结果或行为导向的动词。[7]
| 场景 | 用 check 吗 |
|---|---|
| 返回 boolean | ❌ |
| 返回 Result / Error | ❌ |
| Controller / API | ❌ |
| 有业务副作用 | ❌ |
| 失败直接抛异常 | ✅ |
| 内部前置校验 | ✅ |
| private / protected | ✅ |
| Domain 规则校验 | ✅ |
Guava
Preconditions使用checkArgument、checkState、checkNotNull表示前置条件检查;JDKObjects.checkIndex也把check用于边界检查并在失败时抛异常。这说明check在主流库中常见的核心语义是“检查前置条件或边界”,不是任意业务动作:https://guava.dev/releases/33.4.8-jre/api/docs/com/google/common/base/Preconditions.html、https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Objects.html#checkIndex(int,int)。 ↩︎Guava
Preconditions文档把checkArgument、checkState、checkNotNull分别对应参数、状态和非空前置条件;这些方法的失败语义是抛异常,支撑了“check 可用但语义很窄”的边界:https://guava.dev/releases/33.4.8-jre/api/docs/com/google/common/base/Preconditions.html。 ↩︎Preconditions.checkArgument在表达式为 false 时抛IllegalArgumentException,checkState在状态条件不满足时抛IllegalStateException,JDKObjects.checkIndex在越界时抛IndexOutOfBoundsException;这些例子都不是返回业务结果或推进业务流程的公开动作:https://guava.dev/releases/33.4.8-jre/api/docs/com/google/common/base/Preconditions.html、https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/Objects.html#checkIndex(int,int)。 ↩︎Swift API Design Guidelines 强调名字应在调用点清晰,因为声明只出现一次而使用会出现很多次;Microsoft .NET 成员命名指南也建议方法名使用动词或动词短语,布尔属性使用肯定式短语并可用
Can、Is、Has增强语义:https://www.swift.org/documentation/api-design-guidelines/#naming、https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/names-of-type-members。 ↩︎OpenAPI 3.1 规定
operationId在 API 内应唯一且可被工具和库用于识别 operation;Google AIP-136 要求自定义方法用明确动词命名。公开 API 名字会进入文档、代码生成和客户端心智,因此checkXxx这类语义不稳定动词风险更高:https://spec.openapis.org/oas/v3.1.0.html#operation-object、https://google.aip.dev/136。 ↩︎check在 Guava / JDK 中常见于前置条件或边界检查工具方法;这支持把checkXxx保留在内部 helper 或模块内约定中,而不是泛化为公开业务 API 名称:https://guava.dev/releases/33.4.8-jre/api/docs/com/google/common/base/Preconditions.html。 ↩︎Google AIP-136 要求自定义方法名使用动词,AIP-190 把命名视为跨资源、字段和方法的一致性契约;.NET 成员命名指南也要求方法使用动词或动词短语。这些指南共同支撑“对外 API 用结果或行为导向动词”的建议:https://google.aip.dev/136、https://google.aip.dev/190、https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/names-of-type-members。 ↩︎