2、API设计原则:接口的稳定性与兼容性、命名规范与一致性、参数设计的最佳实践、返回值与错误码设计

好,咱们直接进入正题。API 设计这件事,说白了就是 SDK 的「门面」。用户第一眼看到的就是你的接口长什么样。接口设计得好,用户用得顺手,口碑自然就好。设计得烂,哪怕底层实现再牛,用户也会骂娘。

我这些年做 SDK,踩过不少坑,也重构过好几轮接口。今天就把我积累的一些原则和心得,掰开了揉碎了讲给你听。

2.1 接口的稳定性与兼容性

先聊聊稳定性。你想想看,用户基于你的 SDK 写了上千行代码,结果你发个新版本,接口名改了、参数类型变了、返回值结构也换了。用户什么感受?想杀人的心都有。

接口一旦发布,就不要轻易修改。 这是我给自己定的铁律。

那怎么保证兼容性呢?我总结了几个实操方法:

  • 只增不减:新版本只加新接口,绝不删除旧接口。旧接口可以标记为 @Deprecated,但别直接删。
  • 只扩不缩:给现有接口加参数时,用可选参数或默认值。别把必填参数改成选填,也别减少参数个数。
  • 语义不变:接口的行为不能变。比如 getUserInfo() 之前返回用户基本信息,新版本不能突然改成返回订单信息。

核心原则: 让用户升级 SDK 时,不需要改任何业务代码。这才是真正的兼容。

我记得有一次,我们团队为了追求「代码整洁」,把某个接口的返回类型从 List 改成了 Set。结果用户那边直接编译报错,因为他们的代码里用了 list.get(index)。那次事故之后,我们专门加了一条规范:返回类型的变更,必须走完整的废弃流程。

2.2 命名规范与一致性

命名这件事,看着简单,其实特别考验功力。好的命名,用户看一眼就知道怎么用。烂的命名,用户得翻半天文档。

我个人习惯遵循几个原则:

  • 动词 + 名词:比如 startService()、stopService()、getConfig()。动词在前,名词在后,一目了然。
  • 统一前缀:同一个模块的接口,用相同的前缀。比如支付模块都用 pay_ 开头:pay_order()、pay_refund()、pay_query()。
  • 避免缩写:除非是行业通用缩写(比如 ID、URL),否则别自己造缩写。我见过有人用 getUsrInf(),用户还得猜 Usr 是 User 还是 Usher。

小技巧: 命名之前,先问问自己:「如果用户不看文档,能不能猜出这个接口是干嘛的?」如果答案是否定的,那就重新想个名字。

一致性也很重要。同一个 SDK 里,getXXX() 和 fetchXXX() 不能混用。要么都用 get,要么都用 fetch。我见过最离谱的,同一个类里既有 getData() 又有 retrieveInfo(),用户直接懵了。

2.3 参数设计的最佳实践

参数设计,是 API 设计里最容易出问题的地方。我见过太多「参数地狱」——一个接口十几个参数,调用的时候得对着文档一个一个填。

怎么避免?我分享几个经验:

  • 参数越少越好:理想情况下,一个接口不超过 3 个参数。超过 3 个,考虑用配置对象。
  • 必填参数放前面,选填参数放后面:这是约定俗成的规矩。用户调用时,必填参数一眼就能看到。
  • 用枚举代替布尔值:setMode(true) 和 setMode(Mode.ENCRYPTED),哪个更清晰?显然是后者。
  • 提供合理的默认值:用户不传的参数,SDK 应该能自动用最合理的默认值。别让用户为了一个「超时时间」去翻文档。
// 不好的设计
void connect(String host, int port, boolean useSSL, int timeout, String proxy);

// 好的设计
void connect(String host, int port);
void connect(String host, int port, ConnectionConfig config);

// ConnectionConfig 提供默认值
class ConnectionConfig {
    boolean useSSL = true;
    int timeout = 30000;
    String proxy = null;
}

注意: 参数类型尽量用基础类型或标准库类型。别用自定义类型做必填参数,除非那个类型本身就是业务实体。否则用户还得先构造一个对象才能调用你的接口,体验很差。

2.4 返回值与错误码设计

返回值设计得好,用户处理起来就轻松。设计得不好,用户得写一堆 if-else 来兜底。

我建议遵循「统一返回结构」的原则。不管接口成功还是失败,返回的数据结构都保持一致。比如:

{
    "code": 0,          // 0 表示成功,非 0 表示错误
    "message": "success",
    "data": { ... }     // 业务数据
}

这样用户只需要判断 code 字段,就能知道接口是否成功。不用去猜返回的是 null 还是空对象。

错误码设计也有讲究:

  • 错误码要有层次:比如 1001 表示「参数错误」,1002 表示「参数缺失」,1003 表示「参数格式错误」。这样用户可以根据错误码的前两位,快速定位问题大类。
  • 错误信息要友好:别只返回 ERROR 或 FAILED。要告诉用户具体哪里错了,比如 参数 'timeout' 不能为负数。
  • 预留扩展空间:错误码不要从 1 开始连续排,而是留一些间隔。比如用 1001、2001、3001,方便以后插入新的错误码。

避坑指南: 我曾经设计过一个错误码系统,把错误码和 HTTP 状态码绑在一起。结果后来发现,有些业务错误(比如「用户余额不足」)用 4xx 不合适,用 2xx 又不对。最后不得不重构整个错误码体系。所以,错误码应该独立于传输层协议,别跟 HTTP 状态码耦合。

另外,返回值里尽量别返回 null。如果某个字段可能为空,用 Optional 或者空对象代替。用户不用写一堆 if (xxx != null) 的防御代码。

知识体系总览

下面这张图,把 API 设计原则的核心逻辑串起来了。你可以对照着看,心里有个整体框架。

API 设计原则 稳定性与兼容性 只增不减,只扩不缩 语义不变,行为一致 命名规范与一致性 动词+名词,统一前缀 避免缩写,风格统一 参数设计最佳实践 参数越少越好,用配置对象 枚举代替布尔,提供默认值 返回值与错误码设计 统一返回结构,code+data 错误码分层,信息友好

好了,API 设计原则这部分就聊到这儿。说白了,核心就一句话:站在用户的角度思考。你设计的每个接口、每个参数、每个错误码,最终都是给用户用的。多想想用户会怎么用、会怎么错、会怎么骂你,你的设计自然就会越来越好。

可可精品资料库