要满足的需求
满足功能需求
首先,要设计一个SDK一定要考虑用户需求,确定边界,SDK需要包含哪些功能。个人认为SDK应当精简,专注实现一部分功能即可。
满足性能要求
此外除了功能性需求,软件设计开发中还需要考虑非功能性需求。比方说,稳定性、性能、安全等。
- 稳定性:如果sdk不稳定,功能时好时坏,用户是不是很懵逼。如果sdk有bug,导致了宿主应用崩溃,对实际用户造成体验多不好。故而一个高水准的sdk一定是一个稳定性非常好的sdk。
- 性能:性能实际包含多个方面,比方说包体积、电量、内存等。这些因素对于SDK设计者挑战非常大,但又是对用户影响十分深远的部分。要设计好一个sdk,性能必须满足一定标准。
- 安全:前两者是面向用户的,安全大多数情况是对自己而言的。在编写sdk中,应当要考虑如何保证容器代码安全,保证核心数据、接口不被暴露,保证核心代码不被获取。
遵循的几个基本原则
接口隔离
设计应当小而精简;
开闭原则。
对于扩展开放,对于内部修改封闭。对外交互部分尽量面向接口编程,实现抽象。
接口的易用性
设计符合“人性”的接口,面向“大众”的接口,减少奇技淫巧的使用。sdk是给别人使用的,不是用来炫技的,朴实就好。
向后兼容
不能让用户升级你的sdk后原有功能不可用了。
编写文档
最后,完成sdk编码工作后,一定要记得编写文档。俗话说,程序员最讨厌两件事,一个是写文档,另一个是使用sdk没有文档。一份好的文档能让用户事半功倍,快速接入使用sdk,并减少后续bug。
接口设计的原则
设计原则
1、接口名称、参数名称要足够清晰
一个牛逼的接口名称,可以替代无数的注释
2、一个接口只做一件事
- 一个接口只做一件事。如果有两个比较接近的功能,但是用一个接口实现有点麻烦,那就用两个接口,不要为了减少接口而生硬的把两个接口合为一个。
3、接口参数要尽可能少
- 接口调用的参数要尽可能少,SDK能自身获取的就不要让开发者继续传递,尽可能少的在一个接口中使用同一数据类型的参数,如果确实很多,建议封装
Object
作为参数。
接口参数要一定要校验、需要转义或者转换的一定要尽可能早的处理:
- 所有接口参数必须要做合法性校验。不要让别的接口去保证调用接口的参数一定是合法的。
- 所有接口做的第一件事就应该是对参数做合法性校验。不要等到逻辑跑完大半了再告诉参数不合法,调用失败。
- 对于需要转义、需要类型转换等的参数,一定要处理,而且尽量尽早的去处理,虽然客户端没有XSS或者SQL注入什么的,不代表你就可以不用考虑
4、通用的名称要统一
-
即使再小的系统,也会有一些通用名词,对于一些通用名词或者模块的叫法、写法一定要统一。
具体来说就是一些通用名词的写法一定要规范,其实大多数可以通过编码规范来避免,例如
openid
和open_id
这种只要编码规范统一就不会同时出现。但是对于具体的名词就不好说了,比如openid
,openID
,openId
,OpenID
。这个我还能忍,但是对于QQ的写法我就不能忍了,QQ已经是一个名词,是一个标准的写法了,在QQ结构中看到qq也就算了,竟然还见过有人写Qq,这是什么鬼,什么鬼?!!再举一个微信的例子,写WX,wechat,weChat,wx,weixin的都有,这么多写法,晕不晕?让新来的同学怎么搞~~
5、关于同步和异步接口
- 可以同步的接口,一定不要异步
- 能不用全局回调就一定不要用全局的回调
- 一定要用全局回调最好按照模块分开,一个模块一个回调。开发者只需实现他关心模块的回调即可。无关模块的回调设置是否对SDK的正常使用没有影响;另外每一个回调里最好又能区分回调属于哪次调用的字段。
- 同一个回调里面的接口尽可能的少,可以合并的尽量合并。
个人感觉总结的已经比较到位了,就再讲讲我们怎么做的以及有什么优缺点。最开始我们所有模块公用同一个全局回调,因为当时模块也不多,而且都是必接模块,最关键是多个回调开发者接入的时候比较麻烦,所以选择了不区分模块把所有回调统一整合到一个全局回调。
随着业务发展,我们的SDK包含了越来越多的模块,有些模块是属于个性化的,小众需求的,再把他合到通用的全局回调并不合适,因为很多开发者并不使用这部分功能,却还要关心对应的回调。因此对于这些完全独立而且小众的需求开始使用自己的独立的全局回调。这样虽然解决了所有的问题,但是感觉太像小作坊的模式。因此建议还是最好一开始就按照模块各自设置独立的全局回调。
最新补充,最新的SDK中,我们已经在逐步弃用全局回调,直接在接口调用的时候让同步添加对应的接口回调。
6、多线程处理
-
UI线程处理
-
SDK
除非必须,不要使用应用的主线程,就算使用也只能是简单操作,不能长时间占用。 -
SDK
应该有一个专门的线程来处理SDK
相关的操作。
-
-
怎么使用handle
-
所有耗时、异步操作都通过
handle
扔给SDK的线程去处理。处理结束以后再把结果通过handle
发给主线程。 - 任何时候主线程制作一件事,UI调整。所有的耗时操作:读取文件、读取DB、网络数据读取、网络请求发起等全部都要不要用UI线程去处理。
-
所有耗时、异步操作都通过
7、关于第三方平台
7.1 关于因第三方平台的限制引起的失败
每个SDK不可能都是完全独立的一部分,尤其是为业务服务的SDK,很可能都还会和周边SDK有一些交集。因此对于怎么处理和周边平台相关的一些逻辑也比较麻烦。这里简单汇总下从客户端的角度认为需要关注的点。
- 对于非SDK内部逻辑的限制引起的接口不可用,不要直接判定为失败,而是让规则制定方去判定。而SDK本身可以加个Log提示或者打印Log,方便定位问题。
原因有二:
- 平台的规则可能会调整,到时候平台支持了,你不支持,就是你的问题
- 如果按照上面的方法操作,即使平台调整了,你也不用专门调整,尤其如果是客户端SDK,游戏也不需要更新。
还是说具体的案例吧,不然显得干巴巴。我们的SDK会集成多个SDK,然后对于某些接口平台会有一些限制,比如分享的时候图片大小不能超过10M,缩略图不能超过32K等。最开始我们在做参数校验的时候发现图片大小超过规范以后打算直接返回失败,后来经过讨论,我们决定在超过大小以后打印一行错误日志,还是会去继续调用平台的接口,然后由平台接口调用失败以后回调给我们,我们再回调游戏。加上LOG以后游戏联调的时候,如果调用失败了,我们可以根据LOG快速定位到原因。上线一段时间以后,很多游戏反映32K太小了,果然和预期一致平台将大小改到了64K。那一瞬间,幸福如花儿一般绽放。
7.2 关于第三方平台的常量
- 对于第三方平台的常量例如错误码等,最好是自己封装一层提供给开发者。不要直接将第三方平台暴露给开发者。
很多时候,开发者会通过一个SDK来同时集成和实现多个SDK的功能,这个时候开发者面对的只有一个SDK。如果你的SDK包含了或者集成了多个第三方的SDK,你要做的就是不要让开发者还需要了解其余SDK的东西。包括接入配置等。对于第三方平台的常量,不管是SDK自身还是最终的使用者,其实不会再调整但是SDK怎么处理还是很重要。不封装,开发者还需要关注周边SDK的内容,显得你不够专业;封装的话,万一第三方SDK有调整你也要调整(不要说不可能,我们就亲身经历了一个第三方SDK把常量名称和值都修改了的例子)。
为了防止上面的情况,建议封装第三方平台的常量的时候不要使用对方的常量值,而是直接使用对方的变量来赋值。例如:
public final static int eXXX = otherPlatform.eXXX;
7.3 第三方平台的配置
- 对于第三方平台的各种配置,比如
appid
等,最好是仅仅用于第三方平台的逻辑中,不要为了省事把第三方平台的配置用于自己的业务逻辑
这个其实根据平台的自身需要了,建议既然是SDK,就是一个平台,还是有自己应用标识比较好(appid,appkey),虽然一开始用不到,但是又没有坏处。不要依赖第三方平台的一些应用配置信息做自己的逻辑。
关于配置
这里主要说一些关于模块开关,平台配置相关的内容。主要涉及到配置文件的处理和下发。
配置通过什么形式下发
不管是模块的开关还是接口的权限,都应该可以后台控制。当然前台最好也要有配置文件,可以减少一些无用的请求。而且在后台不能控制的时候,前台的开关还是很有必要的。
在同时有前后台的开关或者配置的时候,记得优先使用后台的配置,不然你搞个后台开关有毛用
配置怎么存放
配置通常可以以plist文件存放在bundle里,也可以放在assert目录下。对于服务端下发的配置文件,可以存放到plist文件里,或者数据库中。
配置使用什么格式
- 所有的配置文件用
key-value
的方式来保存 - 所有的配置项的key建议增加统一的浅醉,例如
MSDK_
。不加的话,配置项多了会显得有点混乱。