如何开发一个SDK

2,447 阅读6分钟

SDK不是什么高深莫测的东西,*SDK 的全称是 Software Development Kit,翻译过来是软件开发工具包,这是一种被用来辅助开发某类软件而编写的特定软件包。也就是说,正常业务开发中,你封装的工具库也可以当成一个SDK。

SDK设计原则

虽然SDK开发比较简单,但是我们也不能放飞自我,在开发的时候,也渐渐的总结除了以下范式:

  • 轻度依赖原则
  • 稳定性原则
  • 扩展性原则
  • 功能设计原则

轻度依赖原则

一般SDK的设计应当非必要避免使用第三方的依赖,依赖第三方轻则可能导致SDK的包体积过于臃肿,重则在第三方依赖升级的时候,可能需要我们SDK跟着同步升级,徒增心智负担,或者依赖中的某个方法和宿主环境不兼容,从而增加我们debug的成本。如果无法避免引入第三方库,那么尽可能最小化引入,切忌全量引入。

稳定性原则

  • 避免宿主环境的奔溃,所以需要我们对异常场景考虑足够的全面,尽可能在内部处理所有的错误场景。
  • 保证SDK的代码是可测试的,并且测试覆盖率尽可能的高。
  • 保证在版本升级的时候,所有的api都是向前兼容的。

扩展性原则

  • 保证我们的功能都是模块化的,保证修改代码不会带来很多心智负担
  • 根据业务实现生命周期钩子机制,保证在任意阶段的功能都是可扩展的

如何设计SDK

核心模块的设计

在写下第一行代码之前,你应该需要考虑一下SDK的核心功能,并且SDK的核心功能是否可以抽象,比如一款错误监控的SDK,它的核心功能就是收集错误 → 上报数据,那么它的核心功能的抽象就是收集某些数据 → 上报到某个地方,这样我们就可以将核心功能抽离成一个模块core.js,接下来的动作就是对core的扩展。

为什么要这么做?

试想,假如做web的错误收集,它的核心功能是上述流程,那么小程序、pc、app的错误收集也是上述流程,只不过不同的是收集错误的方式发生了变化,如果抽离了core.js,那么我们在设计其他平台的SDK的时候,依然可以基于core.js进行扩展

模块的划分

上面只考虑了核心模块的设计思路,其他模块的划分也是有范式可循的。

一般我的思路都是,一个最小可用的功能块作为一个模块,一个大的功能块是由多个小功能模块组成的,有点搭积木的意思。就拿上面的提到的错误采集SDK来说,我们可以把核心功能拆分成错误收集和数据上报,而错误收集又可以拆分成全局异常采集、API异常采集、框架异常采集等等,同理数据上报可以拆分为基础上报能力、批量上报能力、队列上报等等。

这么做的好处就是,易于扩展,提高代码的利用率。

API的设计

设计API,只需要把握以下原则即可:

  • 统一暴露

    即在某个统一的出口,暴露SDK的能力。

  • 命名原则

    1、必须要有一个独特的全局标识,比如SDK的名字叫baidu,那么baidu就可以作为全局标识,方便使用的同时,也增强了SDK的辨识度。

    2、尽量做到看到API的名字就能知道该方法的用途,一般用于减少使用者的心智负担,降低学习成本

  • 参数和返回

    1、永远不要相信用户一定传给你的一定是正确的数据

    2、返回类型尽量统一,比如一直返回的是数组,不能再数据为空的时候返回null,可以返回空数组[],一定程度上可以降低用户在处理返回数据的心智负担

打包构建

1、打包工具的选择

这个具体看个人喜好,任何打包工具都能胜任SDK的打包,不过我个人更钟情于Rollup,因为从使用经验上,个人感觉webpack大而全更偏向于应用的构建,Rollup更偏向于SDK的构建。

2、输出什么文件?

在思考打包构建之前需要考虑一个问题,我们SDK希望被如何使用, 假如需要直接在script中引入,那么我们需要构建IIFE类型的文件,如果希望通过ES Module方式引入,那么需要我们输出ES Module类型的文件,CommonJS同理。

错误处理

这里要聊的错误处理,不单单是通过try...catch保证稳定性的问题,而是如何更优雅的处理错误。

一般设计SDK,都需要一个封装一个统一的错误处理函数,作用是防止原始错误栈直接暴露给用户,举个例子,假设我们有一个API如下:

function foo(fn) {
	fn && fn()
}

当fn函数执行出错的时候,如果直接try...catch的话,用户大概率看到是一堆不知所云的错误堆栈,这时候如果我们在try...catch捕获到错误的时候,直接提示用户foo方法接收的fn函数执行错误,会不会更友好一点?

日志输出

设计SDK的时候,必须避免直接在代码中使用console,最好的做法是我们封装一个Log方法,该方法由用户控制,只有在用户手动打开Log的时候,再去做一些日志输出。

提升用户体验

1、良好的TypeScript的支持

良好的TypeScript指的是全面的类型声明,而非源码简单使用TypeScript编写的,如果实在没空写声明文件,也可以尝试使用JS DOC,据我的经验来看,一般使用SDK的小伙伴,很少有耐心会读完整个SDK的说明文档,所以还是通过TS,让用户正确使用吧。

2、简单的引入方式

站在使用者的角度,我大概率是想SDK引入后直接使用即可,而非需要配置一大堆的options,所以我们在设计的时候可以权衡,保证核心功能是默认开启的,用户只需要一行init()方法即可启用SDK。

结语

本文只介绍两个开发一个SDK的整体思路,对于一些细致末节并没有介绍到,实际上,开发一个SDK需要考虑的问题还有很多,比如我monorepo架构的项目如何去维护、如何进行版本控制、如何优雅的输出更新日志、SDK性能调优、如何设计SDK的扩展、如何设计SDK的生命周期钩子等等都是需要考虑到的。

至于如何提升自己的SDK开发经验,除了多写代码多思考之外,时常去看看社区里面优秀的源码,也是非常好的一种方式,这里可以推荐Sentry.js(一款热门的错误收集SDK)的源码。

文笔拙劣,大家海涵,如果文中有什么不对的,还望不不吝赐教。