OpenHarmony API 设计规范

  • 修订记录
版本 作者 时间 更新内容
v0.1,试运行版 OpenHarmony API SIG 2022年11月 初版发布

目的

API是软件实现者提供给使用者在编程界面上的定义,API在很大程度上体现了软件实体的能力范围。

同时,API定义的好坏极大影响了使用者的体验。

为了保障OpenHarmony生态健康发展,保证开发者使用体验,现制定OpenHarmony API设计规范。

范围

OpenHarmony API主要包含了对应用开放的外部API,以及系统实现部分的内部API。

本设计规范主要用以约束以下API(不区分编程语言):

  • OpenHarmony Public API
  • OpenHarmony System API

关于OpenHarmony API的分类,请参见《 OpenHarmony API治理章程》

接口设计目标

好的接口设计应该满足以下四点:

  • 可使用 (Operational):这一点是毋庸置疑的,接口必须要能完成它提供的能力。可使用是最基本的要求。
  • 富于表现力 (Expressive):与可使用同样重要的是,借助接口,使用者能够清晰表达他们想要做的事情。
  • 简单 (Simple):接口的设计要保证学习和使用简单,避免难于理解或者使用出错的情况发生。
  • 可预测 (Predictable):API应当始终一致的按照接口的定义完成使命。如果接口定义中不包含出错和异常的情况,那么这个API被调用任意多遍,都不应该发生任何异常。当然,如果实际情况是有可能出错或者失败的,那就要在接口定义中说明清楚什么情况下会出什么错,并准确的在相应的时机下返回对应的错误。

除此之外,API的设计还应该注意以下几点:

  • API的稳定性和一致性是最重要的,API并非越多越好。
  • API命名应当容易理解,API命名并非越短越好。
  • API应当做好封装和抽象,并非暴露的信息或者能力越多越好。

从开发者使用API的阶段上来看,API应当做到:

  • 在学习阶段
    • 容易理解
    • 容易上手
  • 在开发阶段
    • 富于表现力
    • 简单
    • 可预测
  • 在维护阶段
    • 行为稳定
    • 容易维护

API设计概述

为了使得规则尽可能通用,本规范不会涉及具体编程语言的差异,也不会涉及编码规约。这部分内容只要遵守相应的本地要求即可。

从整体上来看,本规范将API规范分成发布前评审规范发布后评价两个部分。

  • 发布前评审规范 :是对于API的最基本要求,所有API必须满足这些要求才能通过评审。
  • 发布后评价 :尽管在发布前已经做了很多的规则要求。但是仍然不能保证API是完美的。因此,API发布后仍然要保持对API的关注。发布后的审视将影响对API提供者的评价。

以下是所有的API设计规则的罗列,以供快速索引,详细的说明在后文中展开。

每一个OpenHarmony API提供者都应该熟悉以下规则。

发布前评审规范

  • 易用性
    • 规则1:符合编码规则
    • 规则2:准确使用英语语法
    • 规则3:合理使用缩写
    • 规则4:准确使用对仗词
    • 规则5:准确使用设计/架构模式
    • 规则6:避免有争议的命名
    • 规则7:参数类型合理
    • 规则8:参数数量合理
    • 规则9:参数顺序合理
    • 规则10:返回值定义完备
    • 规则11:异常定义合理
    • 规则12:从正面表达
    • 规则13:降低出错的可能性
    • 规则14:避免顺序耦合
    • 规则15:准确表达所有逻辑
    • 规则16:注意封装
    • 规则17:单一职责
  • 可用性
    • 规则18:保证可靠性
    • 规则19:功能完备
    • 规则20:考虑权限与隐私保护
    • 规则21:考虑并发环境
    • 规则22:注意资源管理闭合
    • 规则23:考虑重试逻辑
    • 规则24:满足幂等要求
    • 规则25:考虑设备通用性
  • 一致性
    • 规则26:保证术语、概念一致性
    • 规则27:保证设备间行为一致性
    • 规则28:注意版本演进一致性
    • 规则29:命名风格保持一致
    • 规则30:参数顺序保持一致
    • 规则31:同步/异步风格一致
  • 兼容性
    • 规则32:注意新旧系统版本的兼容性
    • 规则33:二进制向后兼容性
  • API资料
    • 规则34:模块、命名空间、类需要包含基础说明
    • 规则35:使用场景说明清晰
    • 规则36:接口说明准确
    • 规则37:参数说明准确
    • 规则38:返回值/异常描述准确
    • 规则39:元信息完备
    • 规则40:风格一致
  • 组织方式
    • 规则41:分层合理
    • 规则42:模块划分合理
    • 规则43:应用描述文件也是接口的一部分
  • 质量属性
    • 规则44:性能满足要求
    • 规则45:功耗设计合理
    • 规则46:需要保证可测试性
    • 规则47:考虑环境适应性

发布后评价

  • 稳定性
    • 规则48:接口废弃率/变更率越低越好
  • 安全性
    • 规则49:避免接口被滥用
    • 规则50:避免接口被利用
  • 可维护性
    • 规则51:能承载业务演进
    • 规则52:功能扩展后不影响原先行为
    • 规则53:文档和资料配套更新
  • 不可替代性
    • 规则54:保证API之间正交
  • 开发者反馈
    • 规则55:关注开发者反馈

发布前评审规范说明

易用性

任何设计都应该考虑易用性。对于OpenHarmony API来说,易用性需要考虑以下几个方面:

命名

  • 规则1:符合编码规约

API的定义首先应当满足所属项目的编码规约,例如:大小写的定义、下划线、连字符规则、前缀规则等。

对于OpenHarmony生态而言,可参考如下编码规约:

API命名只允许使用英语。通常,类的名称是名词,例如:XXXManagerXXXServiceXXXAnimation等。函数通常是动词或动宾结构,例如:start()createUser()startBoot()等。

对于一个名称叫做Start的类,或者一个叫做ball()的函数,通常是错误的做法。

另外,还请注意及物动词和不及物动词,避免语法错误。对于动词,根据场景需要注意时态。例如:如果时机上就存在“开始传输”和“渲染完成”这些事件,就应该用上transferStarted()以及renderDone()这类的表达。

  • 规则3:合理使用缩写

应当尽可能避免使用缩写。缩写不仅仅是难于理解,还有一个问题是:一个缩写可能会有多个解释,在上下文信息不全的情况下,使用者会很难分辨。

如果是大家都熟知的缩写是允许的,除此之外,应该尽量避免。尤其是,只有自己能明白的缩写,这是禁止的。

  • 规则4:准确使用对仗词

在语言表达中,同一个含义可能有很多个类似的词可以表达,例如:

词语 近义词
send deliver, dispatch, announce, distribute, route
find search, extract, locate, recover
start launch, create, begin, open
make create, set up, build, generate, compose, add, new

但在API命名中,应当注意对仗词的使用。

例如,如果你使用了add就应该使用remove,而不是destroy。使用了increase就应该使用decrease,而不是reduce

下面是一些常见的对仗词:

词语 对仗
add remove
increase decrease
open close
begin end
insert delete
show hide
create destroy
lock unlock
source target
first last
min max
start stop
get set
next previous
up down
new old
  • 规则5:准确使用模式

设计模式或者架构模式其实是软件行业的“行话”。既然是行话,就要正确的使用,否则别人就可能误解你的意思。

因此,当你的接口中包含了StrategyBuilderFactorySingleton这类的词语时,请确保你准确理解了这些设计模式,并且正确使用了它们。

另外,如果你确定的是在使用某个模式,那就准确的在名称上使用标准术语,不要随便修改词性或者打乱名称的词语顺序,这样使用者会更容易理解。

  • 规则6:避免有争议命名

命名要遵守的底线,是应该避免有争议的名称。这其中,包括但不限于任何违反法律、产生宗教争议、或导致种族歧视的词语。

当然,使用脏话也是绝对禁止的。考虑到OpenHarmony会使能千行百业,对于避免有争议命名,需要思考得更多。

参数

  • 规则7:参数类型合理

通常,使用类类型参数比使用简单类型要更好。

例如:对于下面的一组接口看似没什么问题:

  • addPerson(string id, string name, int age)
  • removePerson(string id, string name, int age)
  • modifyPerson(string id, string name, int age)

但是这个定义不便于扩展,因为后期可能要新增参数。但是如果一开始就通过一个类类型Person来定义参数,则添加一个字段就很容易,并且,对于接口本身不用做任何改变。

使用类类型而不是简单类型,还有一个好处是:参数数量会少,更容易记忆。

  • 规则8:参数数量合理

参数数量应当控制在7个以内。在更多的情况下,参数控制在3~5会更容易使用。

在任何情况,参数的数量都不应当超过10个,这种接口通常很难记忆和使用。如果遇到这种情况,通常是对类型没有做很好的封装,或者是接口的实现逻辑包含太多的内容。请考虑拆解。

  • 规则9:参数顺序合理

在一些编程语言中,常常会以先输入参数、后输出参数的顺序来组织参数列表。

但其实,如果能再根据参数的大小逻辑关系、参数的重要性来进行排序,那对于使用者来说,会更容易记忆和使用。

比如,可选参数应该放到必选参数的后面,回调函数作为参数应该放到最后等。以fs.readFile 方法为例,路径参数是必填的,encodingflag 是有默认值的。

fs.readFile(path[, options], callback)
fs.readFile('/etc/passwd', (err, data) => {
  if (err) throw err;
  console.log(data);
});

fs.readFile('/etc/passwd', {
    encoding: 'utf-8',
    flag: 'r+'
}, (err, data) => {
  if (err) throw err;
  console.log(data);
});

返回值

  • 规则10:返回值定义完备

返回值定义完备需要做到:不能只考虑正常情况,对于接口的定义,也要考虑异常和无效的情况。要让开发者能合理处理。

例如:

  • 对于数字类型的返回值,要定义清楚返回的范围,什么情况下会出现极值。
  • 对于布尔类型的返回值,要定义清楚什么时候返回true,什么时候返回false
  • 对于数组或者集合类型的返回值,要定义清楚什么时候返回null,什么时候返回包含空元素的集合。
  • 对于枚举类型的返回值,要确保每种情况都准确定义了。
  • 规则11:异常定义合理

异常是特殊情况的返回,这通常意味着输入参数无效或者函数根本无法正常处理。

对于同一个模块,或者同样的业务,在何种情况下返回错误值,在何种情况下直接返回异常,应该有统一的定义。

另外,针对同样的异常情况下,应当返回同样的异常类型。

保持一致可以很大程度上降低开发者的使用难度,并且避免使用出错。

其他

对于易用性而言,除了上面提到的内容,还有一些推荐的做法,可以降低开发者的使用难度。

  • 规则12:从正面表达

从正面表达可以降低开发者的思考成本。

通俗来说就是:接口的命名尽量使用肯定的词语,而不是否定方式的表达。因为否定表达很难理解。

下面是一个反例:

if (!isNotAccessible() || !isNotWritable() || !isNotPrintable())

如何接口都是正面表达,就更容易理解了,下面是推荐的做法:

if (isAccessible() && isWritable() && isPrintable())
  • 规则13:降低出错的可能性

调用者使用错误很大程度在于参数传递上。

例如下面这个函数。第二个和第三个参数都是布尔值,这样使用者很容易记错顺序,或者搞不清楚该传入true还是false

declare function findString(text: string, isForward: boolean, isCaseSensitive: boolean): string;

通过枚举在定义上就避免这种错误存在的可能:

enum SearchDirection {
    FORWARD,
    BACKWARD
}; 

enum CaseSensitivity {
    SENSITIVE,
    INSENSITIVE
}; 

declare function findString(text: string, direction: SearchDirection, sensitivity: CaseSensitivity): string;

很多时候,通过枚举代替布尔值以及整数表达的类型,可以减少使用者出错的可能性。

再者,通过将多个参数组成一个类型的形式,也可以降低参数传递出错的可能性。

  • 规则14:避免顺序耦合

软件设计要尽可能保证高内聚,低耦合。这一点对于大型项目尤其重要。

耦合表达了软件模块之间的依赖程度,耦合过深常常意味着架构设计没有做好。

从一个软件静态结构或者分层架构图上可以看得出:如果模块之间的依赖关系比较少,上下层之间有明显的调用方向,则是比较好的设计。相反的,如果模块之间依赖关系非常复杂,或者上下层之间存在相反方向的调用链,则不是一个好的结构。

耦合有很多种,对于接口来说,顺序耦合就是要注意的。顺序耦合是指:类中方法需要按照某个特定的顺序调用才能工作。

类似下面这组命名的接口,很可能就是顺序耦合:

doSomethingFirst()
doSomethingSecond()
doSomethingThird()

这种设计的问题在于:对于使用者的要求太高了,很容易用错。

对于这类问题,通常可以使用模板方法设计模式来解决。

  • 规则15:准确表达所有逻辑

有些接口是用来查询信息的,例如getUserAccount()。有些是进行操作的,会修改数据,例如createUserAccount()

永远不要在一个看起来是查询操作的接口里面去做修改数据的动作 ,因为这样使用者会非常的迷惑。

更通用的来讲, 接口的名称应当表达它所做的所有事情 ,不要有所隐瞒。只有这样,在阅读代码的时候,才能更容易理解代码到底做了什么。

如果接口包含了内容太多,很难通过几个单词描述清楚所做的所有事情该怎么办?对于这种情况,通常意味着需要将这个接口拆分成多个,因为这个接口不够“内聚”。

还有一些情况下,大家在做某件事的时候,会本能的以为别人也有同样的背景认识。但事实往往并不是这样。在编程时也是一样。

例如,对于一个描述编程语言的字段,可能会将其命名为language。这是因为大家默认认为已经在讨论编程语言了,但是language到底是编程语言还是国际化语言?是不是叫做programmingLanuage更好一些呢?

当然,对于这一条还是要举一个反例,不要走到另外一个极端:如果类名或者namespace名称中已经明确带了一个前缀,在函数中就没必要再重复一遍了。毫无信息量的冗余是没有必要的。

  • 规则16:注意封装:不暴露实现细节

对于面向对象编程来说,大家都知道“封装”的重要性。

这意味着,系统能力应当尽可能封装好实现细节,提供简洁的接口供调用者使用。

这就好像冰山,埋在水里的部分不管多庞大,露在水面上的部分要足够的小,足够的简洁。这才是友好的界面(Interface)。

封装的重要性,不仅仅是让开发者容易使用。其实也是使得开发者不容易出错。举一个生活中的例子:电子工程师将房间的电路线接好之后,只留一个开关按钮给到用户,这就是一个很好的封装。既避免了让用户理解复杂的电路结构(方便使用),也不会产生触电(出错)的风险。

  • 规则17:单一职责

一个 API 应当尽量只做一件事情,尽可能保持单一核心的职责。例如:

// 不推荐
view.fetchDataAndRender(url, templete);

// 推荐
let data = view.dataManager.fetchData(url);
view.render(data, templete);

这么做的目的是因为:开发者可以根据需要,按最小单位来使用接口。也可以根据多个单一职责的组合,来进一步封装自己需要的业务逻辑。

单一职责与注重封装性在一些情况下可能是矛盾的:如果提供了多个单一能力的接口,势必需要使用者关心多次的调用细节。

在这种情况下,需要从封装的“层次”来考虑决定,接口提供给开发者的,到底是哪个层级的能力?面向高层次使用者的一件事,对于低一个层次来说,可能就是多件事情。

可用性

  • 规则18:保证可靠性

操作系统所提供的接口,必须是完全可靠的。

当然,可靠性不意味每次调用都是成功的。例如:在资源已经耗尽的情况下,应当给调用者合理的返回值或者异常。

每个接口必须针对所有的情况进行准确的定义,并在相应的情况下按照定义的行为完成工作。

没有按照预定行为返回结果,或者无故导致应用程序异常都属于不可靠行为。

  • 规则19:功能完备

每个特性或者能力在规划时,需考虑到功能的完备性。不应当出现:在支持的范围内,某个流程中断,或者某个选项缺失的情况发生。API设计者应当做好足够的验证和推演。

即便操作系统的特性常常会伴随系统版本几年内才能完全迭代完备。但是,对于每一个特定版本,在其包含的范围内应当是闭合的,自洽的。这要求模块的设计者划分好版本迭代的边界。

  • 规则20:注意权限和隐私保护

任何涉及到用户数据、用户隐私的接口都必须做好权限限制和数据保护。

对于权限控制,应当遵循以下几个原则:

  1. 完备性原则:一切穿透应用沙箱的行为都需考虑使用权限来管控。
  2. 最优粒度原则:一个权限只保护一类对象;一个接口只需申请最小粒度的权限。
  3. 清晰完整原则:权限定义中必须清晰说明保护对象、开放范围、敏感级别。
  4. 最小开放原则:一个权限仅对确有正当业务需求的应用开放,开放控制可通过权限来实现。

对于隐私包含,应当遵守以下几个原则:

  1. API调用的返回仅包含必要的内容, 避免携带额外信息。
  2. API调用不允许获取、手机用户个人数据, 除非通过用户权限管控、由用户授权同意。
  3. API涉及跨应用调用时,如涉及个人数据向被调用者的披露,由调用方在隐私声明中说明披露的数据类型、数据接收者和数据使用目的。
  4. API涉及到用户敏感数据(如电话、通讯录、媒体等)访问时,需要使用system picker的机制,禁止API通过申请敏感权限方式访问。
  5. API开放禁止捆绑与所开放能力不相关的功能。
  • 规则21:考虑并发环境

保证所有的接口线程安全需要付出很大的代价(程序复杂度、性能影响),因此OpenHarmony API不必要求所有接口线程安全。只需根据需要选择即可。

但是,对于设计上就是为了并发环境使用的接口,必须做好相应的设计和说明。

当然,对于接口的内部实现中,应当尽可能避免出现线程不安全的问题发生。

  • 规则22:注意资源管理闭合

在一些情况下,有些接口包含了动态资源的申请。此种情况下,这些接口也需要考虑到资源的释放。

如果申请的资源是直接返回开发者的,则需要提供相应的接口来释放资源。

如果资源没有直接返回给开发者,则接口自身要考虑到资源的生命周期,以及释放的时机。

对于有资源使用上限的情况,应当给开发者说明清楚。并且提供接口查询是否已经到达上限。

对于独占资源更加应当注意资源的释放时机。

  • 规则23:考虑重试逻辑

对于可能失败的接口,应当考虑好给开发者相应的机制来重试。例如:对于相机这类独占的资源,一旦有某个进程使用了,其他进程就无法获取到。这种情况下,应当提供接口给开发者查询是否可用的接口。

在一些情况下,为了避免开发者反复尝试,还需要提供给开发者状态监听的机制。

并且,API要明确客户端调用失败后,能够发起重试的最大次数。

  • 规则24:满足幂等要求

在数学中,幂等用函数表达式就是:f(x) = f(f(x))。例如求绝对值的函数,就是幂等的,abs(x) = abs(abs(x))

计算机科学中,幂等表示一次和多次请求某一个资源应该具有同样的效果,或者说,多次请求所产生的影响与一次请求执行的影响效果相同。

具体到实际场景:文件打开,或者的硬件资源使用,打开一次和打开多次其效果应当是一样的,不应该有其他副作用。当然,关闭,也是类似。

  • 规则25:考虑设备通用性

OpenHarmony是为多种不同类型设备设计的统一操作系统。

考虑到设备的复杂性,接口的设计应当要能面对多种设备的适用性。例如:

  • 对于用户界面的控件,要考虑到不同屏幕尺寸的大小。
  • 对于数据的存储,要考虑到不同大小的存储空间。
  • 对于用户输入事件,要考虑不同的用户交互方式:触摸、语音、按键等。

当然,有一些API只在某些特定设备上才有,例如:

  • 健康类传感器只在穿戴设备上有
  • 车控类接口只在车机设备上有

这种情况,请参考《 SysCap使用指南》,来标定API的适用范围。

一致性

  • 规则26:保证术语、概念的一致性

为了容易理解,接口在命名和说明上应当注意术语和概念的一致性,不应该出现“方言”。基于场景的业务模型抽象,形成OpenHarmony的连贯、一致、自恰的业务概念。

在这方面,应当遵循以下原则:

  1. 每个概念只应该有 唯一 的一个术语,同一个对象不应该有两个名称。
  2. 术语命名应该是 贴切的可解释易理解
  3. 术语的定义应当 精准、无二义
  4. 对于业界通用术语, 不应该重新定义 ,应当遵循业内做法。

总体来说,要避免随意添加术语概念,尽可能以《OpenHarmony 技术术语集》为准。如果需要,可以在术语集中新增词条。

  • 规则27:保证设备间行为一致性

在默认情况下,同一个接口在不同的设备上应当保证行为是一致的。

对于因为设备形态而导致的行为不一致,应当给予开发者充分的说明,并且提供相应的检查机制。

  • 规则28:注意版本演进一致性

在默认情况下,所有接口应当保证在系统版本演进的情况下,其行为是一致的。如果在新版本上出现了行为不兼容的变更,最低要求是需要对应用的目标版本做区分处理。

简单而言:接口的行为变更不能影响已经开发完的应用。

  • 规则29:命名风格保持一致

在一些 API 设计的场景中,即使命名已经做到准确,但在有些情况下仍然可能碰到不一致的场景。比如,API 中经常会看到 picture 和 image、path 和 url 混用的情况,这两组词的意思非常接近,容易出现混乱。在指代同一内容时,应该使用相同的命名。

例如,下面这些接口都是为了获取媒体资源,但是命名风格不一致。

declare function getMediaAsserts(): Array<MediaAssert>;
declare function getAudios(): Array<AudioAssert>;
declare function getVideos(): Array<VideoAssert>;
declare function getImages(): Array<ImageAssert>;

应该修改为:

function getMediaAsserts(): Array<MediaAssert>;
function getAudioAsserts(): Array<AudioAssert>;
function getVideoAsserts(): Array<VideoAssert>;
function getImageAsserts(): Array<ImageAssert>;
  • 规则30:参数顺序保持一致

为了开发者便于理解,对于同一个命名空间或者是同一个模块来说,其成套的接口的参数顺序应当是一致的。

例如:deviceIdmissionId在单个接口中的顺序没有强制要求,但是在同一模块接口中必须保持顺序一致。

function getMissionInfo(deviceId: string, missionId: number): Promise<MissionInfo>;
function getMissionSnapShot(deviceId: string, missionId: number): Promise<MissionSnapshot>;

// 正确
function getLowResolutionMissionSnapShot(deviceId: string, missionId: number): Promise<MissionSnapshot>;

// 错误
function getLowResolutionMissionSnapShot(missionId: number, deviceId: string): Promise<MissionSnapshot>;
  • 规则31:同步/异步风格一致

异步接口应该可以通过入参和返回值判断出来,风格上要保持统一。例如:

// Callback形式
function getDefaultDisplay(callback: AsyncCallback<Display>): void;
// Promise形式
function getDefaultDisplay(): Promise<Display>;

如果同时提供了同步和异步接口,可以在同步函数名后加上后缀Sync加以区别,例如:

function getDefaultDisplaySync(): Display;

如果只提供了同步接口,且返回值不是void,可以不用加后缀,例如:

function registerMissionListener(listener: MissionListener): number;

兼容性

  • 规则32:注意新旧系统版本的兼容性

API 变更的成本非常高,在设计之初就应该做尽可能完备的考虑。但是API变更始终是系统发展过程中不可避免的。

API 变更要保证向后兼容原则,API 变更后,废弃的 API 要在源码和文档中显著标识 deprecated,并引导开发者使用变更后 API 。

废弃的 API 要保证其功能仍然可用,至少保留5个版本。5个版本后,在充分告知开发者,并给与充分的时间供开发者修改后,可予以删除。

  • 规则33:二进制向后兼容性

二进制兼容,指版本演进后,开发者已有程序不用重新编译可正常链接、运行。这也意味着,二进制兼容是保证在版本升级的情况下,对象实例的内存布局不会发生变化。

以C++接口为例,常见破坏二进制兼容的API变更包括:

  1. 任何API元素删除
  2. 添加新的虚函数
  3. 改变类的继承
  4. 改变虚函数声明时的顺序
  5. 添加新的非静态成员变量
  6. 改变非静态成员变量的声明顺序

由于C接口相比C++接口在二进制兼容性上有天然的优势,所以OpenHarmony的Native API推荐使用C接口定义。

API资料

API的很多信息需要通过文档和资料进行说明,因此,配套的这些内容也应当做好质量管理。

  • 规则34:模块、命名空间、类、函数需要包含基础说明

每个模块、命名空间、类和函数都需要包含基本的说明。

对于关键模块、复杂模块需要有详细的说明。

说明采用英文的形式。

  • 规则35:使用场景说明清晰

所有接口应当提供示例代码,覆盖常见使用场景。

复杂接口需要详细说明使用场景。可以在接口的说明上增加详细教程的链接。

  • 规则36:接口说明准确

接口说明的文字不应该出现拼写错误或者错别字。

所有代码示例应当保证能够正常运行。如果接口在不同版本上存在行为不一致,则需要针对每种情况做详细说明。

对于废弃接口,需做明确标记,并做好替代接口说明。

  • 规则37:参数说明准确

对于API的每一个参数,都应该说明清楚。例如:

  • 如果是非简单类型,则需说明清楚是否允许参数为空
  • 如果是枚举类型,则需针对每个枚举值说明清楚使用场景
  • 如果是可选参数,则需说明清楚什么时候该传参,什么时候可省略

规则38:返回值/异常定义准确

如果接口有返回值/异常,需要在接口说明中描述完整。例如:

/**
 * Sync function of rename.
 * @param {string} path - path.
 * @returns {void} rmdir success.
 * @throws {BusinessError} 401 - if type of path is not string.
 * @throws {BusinessError} 201 - if permission denied.
 * @syscap SystemCapability.FileManagement.File.FileIO
 * @since 7
 */
declare function rmdirSync(path: string): void;
  • 规则39:元信息完备

接口的方法说明上应当包含基本的元信息,例如:@syscap@since等。

元信息描述了API自身的基本信息。工具和SDK会利用这个信息进行相应的处理,例如:针对已经废弃的接口会给出警告提示。

  • 规则40:风格一致

API的说明资料,在风格和样式上应当保持一致。例如,文字的加粗,资料图片的配色等。

组织方式

  • 规则41:分层合理

操作系统通常是以分层的模型来进行架构的。这意味着每一层要解决不同层次上的问题。

因此,接口的设计也要注意相应的层次。

可以使用建筑行业来进行类比:所有的建筑都会包含墙体和房间的门。墙体是有砖块构成的,门是有木料构成的。在这种情况下,抽象可能会分为下面几层:

  • 第一层是基本都原材料,包括水泥、沙子、木材等
  • 第二层是用原材料构建出来的建筑元素,例如:门、窗户、墙体等
  • 第三层是房间的种类,例如:卧室、卫生间、客厅等
  • 最后,最上面一层是各种不同用途的建筑。例如:酒店、公寓等

这里每一层所要考虑的概念和要处理的问题都是不一样的,请仔细思考你提供的API是在哪一个层次上。

  • 规则42:模块划分合理

大家不仅要关注水平层面的分层,也应该关注垂直层面的划分。因此,模块的组织也同样重要。OpenHarmony的接口是以命名空间的形式组织的。一个命名空间通常对应了一个模块。

从子系统的角度,一个较小的子系统应该尽可能将接口放在同一个命名空间中。较大的子系统,可以提供多个命名空间。

  • 规则43:应用描述文件也是接口的一部分

接口的英文是Interface,这个词还有界面的意思。操作系统提供给开发者的接口并不仅仅是编程接口。任何公开给开发者的机制,都是API的一部分。

这其中,最明显的就是应用描述文件,这些文件也需要按照接口规范一样的规则来管理。

质量属性

  • 规则44:性能满足要求

操作系统所提供的接口所有上层应用都可能会使用到,因此,其实现的性能应当是能够满足实际要求的。

下面是一些举例:

  1. 应及时响应,避免调用者等待;如果API调用执行时间过长应设计为异步方式。
  2. 接口存在大量数据传输时,应考虑使用共享内存、消息队列等。
  3. 尽可能减少新增进程实体。
  4. 对使用资源的API调用需要能及时释放资源,异常场景具备容错机制,保障资源及时释放。
  • 规则45:功耗设计合理

OpenHarmony 操作系统在设计之初就需要面向多种不同类型的设备,这些设备中绝大多数是无源设备。因此,功耗的考虑是毋庸置疑的。

每个功能/机制在实现上应当把功耗的合理性作为最基本的要求。除此之外,对于高功耗的接口应当在说明中给开发者充足的解释,并且对于使用方式给与足够的指导。

同时,在使用上要避免开发者错误的使用。例如,在设备锁屏之后,或者应用切换到后台之后,就应当停止高功耗的行为。

另外,还应当注意在版本演进过程中,功耗不出现劣化。

  • 规则46:需要保证可测试性

完备的自动化测试用例,可以带来很多好处:

  1. 开发过程中,提前快速发现问题,提高接口质量。
  2. 版本迭代时,保证代码修改不影响功能,提高代码修改的信心。
  3. 确保接口向后兼容性。

所以,对于一个OpenHarmony API,都要求做到以下几点:

  1. 新增API必须同步交付API自动化测试用例,用例100%覆盖API接口。
  2. 用例场景单一,单条用例覆盖接口单个功能场景,简化单条用例代码逻辑。
  3. 用例执行高效,每条用例执行时间控制在毫秒级。
  4. 用例执行全自动化:接口用例需要达成100%自动化。
  5. 用例有效性:用户要求必须存在断言,且不能仅是检查是否抛出异常,需要有功能逻辑的断言。
  • 规则47:考虑环境适应性

考虑到用户的个性化,操作系统通常会提供一些环境自定义的能力,例如:语言国际化、浅色/深色主题、字体大小等。

对于这方面相关的接口,在开发者没有传递指定参数的情况下,应当能够更加当前所属的环境,自适应的返回相应环境下的结果。

发布后评价说明

尽管在API发布前已经做了多方面规则约束,但总可能还有一些问题在开发者使用之后才能发现。

即便是这样,但并不意味着在设计API之初不需要考虑这些问题。

相反的,大家应当时刻避免发布后问题的发生。

如果在接口发布后,发现了下面几条规则问题的发生,则该模块的接口质量是比较差的。

稳定性

  • 规则48:接口废弃率/变更率越低越好

对于接口来说,稳定是其最重要的属性。

这是因为接口的废弃和行为变更将极大的影响开发者的维护效率。考虑上多种设备类型、多个系统版本,这个问题会变得更加复杂。

设计出能保证长期稳定,持续兼容的接口是每个API设计者应当追求的目标。

接口的废弃率/变更率与接口的质量在一定程度上成反比。

安全性

  • 规则49:避免接口被滥用

所有接口在设计上,应当考虑到不能被过度滥用。滥用既可能是数量上的滥用,也可能是范围上的滥用。

例如:对于用户公共数据(相册、联系人等)的访问,对于长时间后台运行的能力,都可能被滥用。

对于数量滥用的防止,可以考虑“仅一次授权”这样的机制。

在实现上,可以根据调用者的身份进行相应的限制。

当然,无论是哪种限制,都应该在接口说明中说明清楚。并且在检测到过度滥用的情况下,有相应的返回值告知调用者。

  • 规则50:避免接口被利用

被滥用是指接口的使用超出了原先预期的限制。而被利用是指:接口可以被用来造成负面影响,例如攻击系统。

无论开发者以何种方式使用OpenHarmony提供的接口,如果某个接口,或某些接口组合调用导致系统出现了问题,那么就一定接口本身存在问题。

特别的,如果某些接口(无论在何种情况下)一旦调用就可以使用系统崩溃,或者进入不能工作的场景,或者能够窃取用户数据,这是绝对不允许的。这就要求API的设计者从API被调用的所有可能都场景上进行考虑,避免一些极端情况的发生。

可维护性

  • 规则51:能承载业务能力演进

考虑到操作系统的一些较大特性,需要经过数年才能打磨完成。因此接口在设计上应当考虑今后的可扩展性。

随着能力的演进,如果出现了在新版本上新起了一组新的接口,而废弃了原先旧的接口,那么就是接口的设计上存在问题。这是应当避免的。

  • 规则52:功能扩展后不影响原先行为

随着OpenHarmony 操作系统版本的演进,一些接口新增了参数,或者一些参数新增了新的选项是很常见的一种行为。

但是,在这种情况下,一定要考虑清楚对于过往已存在行为的一致性。不应当因为新的场景的引入,而破坏了原先存在的行为。

这里需要遵循的原则是:如果在新版本上行为要发生变化,只允许针对目标版本是新版本的应用生效。对于目标版本是旧版本的应用应当继续保持原先的行为。

  • 规则53:文档和资料应当配套更新

当大家在更新接口实现的时候,一定要记得更新接口的说明。从兼容性的角度来看,不能修改原先存在的行为。通常应当提供新的接口来完成新的功能。

但在下面的情况下,可以考虑修改既有接口的行为:

  1. 缺陷的修复。
  2. 性能/功耗的改进。
  3. 为接口拓展新的特性或场景,但不影响原有特性或场景逻辑。

第1、2种情况,通常在Release Note里面说明;第3种情况就需要在接口说明上表述清楚。

不可替代性

  • 规则54:保证API之间正交

正交(Orthogonality),意味着多个接口之间不应该出现重合的功能。

例如:一个接口提供了创建用户账号的能力。另外一个接口包含了创建用户并登录的功能。如果是这样,就应该把第二个接口拆开,只保留单独登录的功能。

如果将接口的能力画成一个个图形,那么这些图形之间不应该出现重叠交错的想象。对于同一个层级的接口,不应该出现某个接口提供了1、2、3功能,另外一个接口又提供了2、3、4功能这种情况发生。

当然,为了便于调用,允许将一些接口组合成一个更高阶的接口。这通常是抽象层次的不一样,并非是同一个层次的重叠。

开发者反馈

  • 规则55:关注开发者反馈

尽管API在正式发布之前,会经过试用阶段,但考虑到试用时间和试用范围是有限的,实际上无法保证能避免所有问题。

因此,在接口正式发布之后,也应该继续关注开发者的反馈。

开发者反馈可能分为以下几种情况:

  • 希望提供更多API来满足目前不能实现的功能,这种情况可以将需求导入到下个版本的规划中。
  • 反馈API的行为与文档描述不一致,这通常是缺陷,应当尽快修复。如果是文档的问题,也应当尽快修改。
  • 反馈接口设计不合理,可能是命名或者参数设置存在问题,这种情况就需要考虑API废弃同时用新API代替。

在一些情况下,可能要变更已经发布的API行为。这种情况下,应当要注意:行为变化只能发生在新开发的应用上,对于已经发布的应用,不能产生行为变化。API提供者可以根据应用的目标API版本来进行判断。

在最坏的情况下,需要将既存API废弃,提供新的API代替。