好的 API 设计:在自描述的同时,达到抽象的目标。

设计良好的 API ,开发者可以快速上手,没必要经常抱着手册和文档,也没必要频繁光顾技术支持社区。

流畅的接口

方法链:流畅易读,更易理解

  1. //常见的 API 调用方式:改变一些颜色,添加事件监听
  2. var elem = document.getElementById("foobar");
  3. elem.style.background = "red";
  4. elem.style.color = "green";
  5. elem.addEventListener('click', function(event) {
  6. alert("hello world!");
  7. }, true);
  8. //(设想的)方法链 API
  9. DOMHelper.getElementById('foobar')
  10. .setStyle("background", "red")
  11. .setStyle("color", "green")
  12. .addEvent("click", function(event) {
  13. alert("hello world");
  14. });

设置和获取操作,可以合二为一;方法越多,文档可能越难写

  1. var $elem = jQuery("#foobar");
  2. //setter
  3. $elem.setCss("background", "green");
  4. //getter
  5. $elem.getCss("color") === "red";
  6. //getter, setter 合二为一
  7. $elem.css("background", "green");
  8. $elem.css("color") === "red";

一致性

相关的接口保持一致的风格,一整套 API 如果传递一种熟悉和舒适的感觉,会大大减轻开发者对新工具的适应性。

命名这点事:既要短,又要自描述,最重要的是保持一致性

“There are only two hard problems in computer science: cache-invalidation and naming things.”
“在计算机科学界只有两件头疼的事:缓存失效和命名问题”
— Phil Karlton

选择一个你喜欢的措辞,然后持续使用。选择一种风格,然后保持这种风格。

处理参数

需要考虑大家如何使用你提供的方法,是否会重复调用?为何会重复调用?你的 API 如何帮助开发者减少重复的调用?
接收map映射参数,回调或者序列化的属性名,不仅让你的 API 更干净,而且使用起来更舒服、高效。

jQuery 的 css() 方法可以给 DOM 元素设置样式:

  1. jQuery("#some-selector")
  2. .css("background", "red")
  3. .css("color", "white")
  4. .css("font-weight", "bold")
  5. .css("padding", 10);

这个方法可以接受一个 JSON 对象:

  1. jQuery("#some-selector").css({
  2. "background" : "red",
  3. "color" : "white",
  4. "font-weight" : "bold",
  5. "padding" : 10
  6. });
  7. //通过传一个 map 映射绑定事件
  8. jQuery("#some-selector").on({
  9. "click" : myClickHandler,
  10. "keyup" : myKeyupHandler,
  11. "change" : myChangeHandler
  12. });
  13. //为多个事件绑定同一个处理函数
  14. jQuery("#some-selector").on("click keyup change", myEventHandler);

处理类型

定义方法的时候,需要决定它可以接收什么样的参数。我们不清楚人们如何使用我们的代码,但可以更有远见,考虑支持哪些参数类型。

  1. //原来的代码
  2. DateInterval.prototype.days = function(start, end) {
  3. return Math.floor((end - start) / 86400000);
  4. };
  5. //修改后的代码
  6. DateInterval.prototype.days = function(start, end) {
  7. if (!(start instanceof Date)) {
  8. start = new Date(start);
  9. }
  10. if (!(end instanceof Date)) {
  11. end = new Date(end);
  12. }
  13. return Math.floor((end.getTime() - start.getTime()) / 86400000);
  14. };

加了短短的6行代码,我们的方法强大到可以接收 Date 对象,数字的时间戳,甚至像 Sat Sep 08 2012 15:34:35 GMT+0200 (CEST) 这样的字符串

如果你需要确保传入的参数类型(字符串,数字,布尔),可以这样转换:

  1. function castaway(some_string, some_integer, some_boolean) {
  2. some_string += "";
  3. some_integer += 0; // parseInt(some_integer, 10) 更安全些
  4. some_boolean = !!some_boolean;
  5. }

处理 undefined

为了使你的 API 更健壮,需要鉴别是否真正的 undefined 值被传递进来,可以检查 arguments 对象:

  1. function testUndefined(expecting, someArgument) {
  2. if (someArgument === undefined) {
  3. console.log("someArgument 是 undefined");
  4. }
  5. if (arguments.length > 1) {
  6. console.log("然而它实际是传进来的");
  7. }
  8. }
  9. testUndefined("foo");
  10. // 结果: someArgument 是 undefined
  11. testUndefined("foo", undefined);
  12. // 结果:  someArgument 是 undefined , 然而它实际是传进来的

给参数命名

  1. event.initMouseEvent(
  2. "click", true, true, window,
  3. 123, 101, 202, 101, 202,
  4. true, false, false, false,
  5. 1, null);

Event.initMouseEvent 这个方法简直丧心病狂,不看文档的话,谁能说出每个参数是什么意思?

给每个参数起个名字,赋个默认值,可好

  1. event.initMouseEvent(
  2. type="click",
  3. canBubble=true,
  4. cancelable=true,
  5. view=window,
  6. detail=123,
  7. screenX=101,
  8. screenY=202,
  9. clientX=101,
  10. clientY=202,
  11. ctrlKey=true,
  12. altKey=false,
  13. shiftKey=false,
  14. metaKey=false,
  15. button=1,
  16. relatedTarget=null);

ES6, 或者 Harmony 就有 默认参数值 和 rest 参数 了。

参数接收 JSON 对象

与其接收一堆参数,不如接收一个 JSON 对象:

  1. function nightmare(accepts, async, beforeSend, cache, complete, /* 等28个参数 */) {
  2. if (accepts === "text") {
  3. // 准备接收纯文本
  4. }
  5. }
  6. function dream(options) {
  7. options = options || {};
  8. if (options.accepts === "text") {
  9. // 准备接收纯文本
  10. }
  11. }

调用起来也更简单了:

  1. nightmare("text", true, undefined, false, undefined, /* 等28个参数 */);
  2. dream({
  3. accepts: "text",
  4. async: true,
  5. cache: false
  6. });

参数默认值

参数最好有默认值,通过 jQuery.extend() http://underscorejs.org/#extend) 和 Protoype 的 Object.extend ,可以覆盖预设的默认值。

  1. var default_options = {
  2. accepts: "text",
  3. async: true,
  4. beforeSend: null,
  5. cache: false,
  6. complete: null,
  7. // …
  8. };
  9. function dream(options) {
  10. var o = jQuery.extend({}, default_options, options || {});
  11. console.log(o.accepts);
  12. }
  13. dream({ async: false });
  14. // prints: "text"

扩展性

回调(callbacks)

通过回调, API 用户可以覆盖你的某一部分代码。把一些需要自定义的功能开放成可配置的回调函数,允许 API 用户轻松覆盖你的默认代码。

API 接口一旦接收回调,确保在文档中加以说明,并提供代码示例。

事件(events)

事件接口最好见名知意,可以自由选择事件名字,避免与原生事件 重名。

处理错误

不是所有的错误都对开发者调试代码有用:

  1. // jQuery 允许这么写
  2. $(document.body).on('click', {});
  3. // 点击时报错
  4. //   TypeError: ((p.event.special[l.origType] || {}).handle || l.handler).apply is not a function
  5. //   in jQuery.min.js on Line 3

这样的错误调试起来很痛苦,不要浪费开发者的时间,直接告诉他们犯了什么错:

  1. if (Object.prototype.toString.call(callback) !== '[object Function]') { // 看备注
  2. throw new TypeError("callback is not a function!");
  3. }
  4. 备注:typeof callback === "function" 在老的浏览器上会有问题,object 会当成个 function 。

可预测性

好的 API 具有可预测性,开发者可以根据例子推断它的用法。

Modernizr’s 特性检测 是个例子:

a) 它使用的属性名完全与 HTML5、CSS 概念和 API 相匹配

b) 每一个单独的检测一致地返回 true 或 false 值

  1. // 所有这些属性都返回 'true' 或 'false'
  2. Modernizr.geolocation
  3. Modernizr.localstorage
  4. Modernizr.webworkers
  5. Modernizr.canvas
  6. Modernizr.borderradius
  7. Modernizr.boxshadow
  8. Modernizr.flexbox

依赖于开发者已熟悉的概念也可以达到可预测的目的。

jQuery’s 选择器语法 就是一个显著的例子,CSS1-CSS3 的选择器可直接用于它的 DOM 选择器引擎。

  1. $("#grid") // Selects by ID
  2. $("ul.nav > li") // All LIs for the UL with class "nav"
  3. $("ul li:nth-child(2)") // Second item in each list

比例协调

好的 API 并不一定是小的 API,API 的体积大小要跟它的功能相称。

比如 Moment.js ,著名的日期解析和格式化的库,可以称之为均衡,它的 API 既简洁又功能明确。

像 Moment.js 这样特定功能的库,确保 API 的专注和小巧非常重要。

编写 API 文档

软件开发最艰难的任务之一是写文档,实际上每个人都恨写文档,怨声载道的是没有一个好用的文档工具。

以下是一些文档自动生成工具:

好的 API 设计:在自描述的同时,达到抽象的目标。

设计良好的 API ,开发者可以快速上手,没必要经常抱着手册和文档,也没必要频繁光顾技术支持社区。

流畅的接口

方法链:流畅易读,更易理解

  1. //常见的 API 调用方式:改变一些颜色,添加事件监听
  2. var elem = document.getElementById("foobar");
  3. elem.style.background = "red";
  4. elem.style.color = "green";
  5. elem.addEventListener('click', function(event) {
  6. alert("hello world!");
  7. }, true);
  8. //(设想的)方法链 API
  9. DOMHelper.getElementById('foobar')
  10. .setStyle("background", "red")
  11. .setStyle("color", "green")
  12. .addEvent("click", function(event) {
  13. alert("hello world");
  14. });

设置和获取操作,可以合二为一;方法越多,文档可能越难写

  1. var $elem = jQuery("#foobar");
  2. //setter
  3. $elem.setCss("background", "green");
  4. //getter
  5. $elem.getCss("color") === "red";
  6. //getter, setter 合二为一
  7. $elem.css("background", "green");
  8. $elem.css("color") === "red";

一致性

相关的接口保持一致的风格,一整套 API 如果传递一种熟悉和舒适的感觉,会大大减轻开发者对新工具的适应性。

命名这点事:既要短,又要自描述,最重要的是保持一致性

“There are only two hard problems in computer science: cache-invalidation and naming things.”
“在计算机科学界只有两件头疼的事:缓存失效和命名问题”
— Phil Karlton

选择一个你喜欢的措辞,然后持续使用。选择一种风格,然后保持这种风格。

处理参数

需要考虑大家如何使用你提供的方法,是否会重复调用?为何会重复调用?你的 API 如何帮助开发者减少重复的调用?
接收map映射参数,回调或者序列化的属性名,不仅让你的 API 更干净,而且使用起来更舒服、高效。

jQuery 的 css() 方法可以给 DOM 元素设置样式:

  1. jQuery("#some-selector")
  2. .css("background", "red")
  3. .css("color", "white")
  4. .css("font-weight", "bold")
  5. .css("padding", 10);

这个方法可以接受一个 JSON 对象:

  1. jQuery("#some-selector").css({
  2. "background" : "red",
  3. "color" : "white",
  4. "font-weight" : "bold",
  5. "padding" : 10
  6. });
  7. //通过传一个 map 映射绑定事件
  8. jQuery("#some-selector").on({
  9. "click" : myClickHandler,
  10. "keyup" : myKeyupHandler,
  11. "change" : myChangeHandler
  12. });
  13. //为多个事件绑定同一个处理函数
  14. jQuery("#some-selector").on("click keyup change", myEventHandler);

处理类型

定义方法的时候,需要决定它可以接收什么样的参数。我们不清楚人们如何使用我们的代码,但可以更有远见,考虑支持哪些参数类型。

  1. //原来的代码
  2. DateInterval.prototype.days = function(start, end) {
  3. return Math.floor((end - start) / 86400000);
  4. };
  5. //修改后的代码
  6. DateInterval.prototype.days = function(start, end) {
  7. if (!(start instanceof Date)) {
  8. start = new Date(start);
  9. }
  10. if (!(end instanceof Date)) {
  11. end = new Date(end);
  12. }
  13. return Math.floor((end.getTime() - start.getTime()) / 86400000);
  14. };

加了短短的6行代码,我们的方法强大到可以接收 Date 对象,数字的时间戳,甚至像 Sat Sep 08 2012 15:34:35 GMT+0200 (CEST) 这样的字符串

如果你需要确保传入的参数类型(字符串,数字,布尔),可以这样转换:

  1. function castaway(some_string, some_integer, some_boolean) {
  2. some_string += "";
  3. some_integer += 0; // parseInt(some_integer, 10) 更安全些
  4. some_boolean = !!some_boolean;
  5. }

处理 undefined

为了使你的 API 更健壮,需要鉴别是否真正的 undefined 值被传递进来,可以检查 arguments 对象:

  1. function testUndefined(expecting, someArgument) {
  2. if (someArgument === undefined) {
  3. console.log("someArgument 是 undefined");
  4. }
  5. if (arguments.length > 1) {
  6. console.log("然而它实际是传进来的");
  7. }
  8. }
  9. testUndefined("foo");
  10. // 结果: someArgument 是 undefined
  11. testUndefined("foo", undefined);
  12. // 结果:  someArgument 是 undefined , 然而它实际是传进来的

给参数命名

  1. event.initMouseEvent(
  2. "click", true, true, window,
  3. 123, 101, 202, 101, 202,
  4. true, false, false, false,
  5. 1, null);

Event.initMouseEvent 这个方法简直丧心病狂,不看文档的话,谁能说出每个参数是什么意思?

给每个参数起个名字,赋个默认值,可好

  1. event.initMouseEvent(
  2. type="click",
  3. canBubble=true,
  4. cancelable=true,
  5. view=window,
  6. detail=123,
  7. screenX=101,
  8. screenY=202,
  9. clientX=101,
  10. clientY=202,
  11. ctrlKey=true,
  12. altKey=false,
  13. shiftKey=false,
  14. metaKey=false,
  15. button=1,
  16. relatedTarget=null);

ES6, 或者 Harmony 就有 默认参数值 和 rest 参数 了。

参数接收 JSON 对象

与其接收一堆参数,不如接收一个 JSON 对象:

  1. function nightmare(accepts, async, beforeSend, cache, complete, /* 等28个参数 */) {
  2. if (accepts === "text") {
  3. // 准备接收纯文本
  4. }
  5. }
  6. function dream(options) {
  7. options = options || {};
  8. if (options.accepts === "text") {
  9. // 准备接收纯文本
  10. }
  11. }

调用起来也更简单了:

  1. nightmare("text", true, undefined, false, undefined, /* 等28个参数 */);
  2. dream({
  3. accepts: "text",
  4. async: true,
  5. cache: false
  6. });

参数默认值

参数最好有默认值,通过 jQuery.extend() http://underscorejs.org/#extend) 和 Protoype 的 Object.extend ,可以覆盖预设的默认值。

  1. var default_options = {
  2. accepts: "text",
  3. async: true,
  4. beforeSend: null,
  5. cache: false,
  6. complete: null,
  7. // …
  8. };
  9. function dream(options) {
  10. var o = jQuery.extend({}, default_options, options || {});
  11. console.log(o.accepts);
  12. }
  13. dream({ async: false });
  14. // prints: "text"

扩展性

回调(callbacks)

通过回调, API 用户可以覆盖你的某一部分代码。把一些需要自定义的功能开放成可配置的回调函数,允许 API 用户轻松覆盖你的默认代码。

API 接口一旦接收回调,确保在文档中加以说明,并提供代码示例。

事件(events)

事件接口最好见名知意,可以自由选择事件名字,避免与原生事件 重名。

处理错误

不是所有的错误都对开发者调试代码有用:

  1. // jQuery 允许这么写
  2. $(document.body).on('click', {});
  3. // 点击时报错
  4. //   TypeError: ((p.event.special[l.origType] || {}).handle || l.handler).apply is not a function
  5. //   in jQuery.min.js on Line 3

这样的错误调试起来很痛苦,不要浪费开发者的时间,直接告诉他们犯了什么错:

  1. if (Object.prototype.toString.call(callback) !== '[object Function]') { // 看备注
  2. throw new TypeError("callback is not a function!");
  3. }
  4. 备注:typeof callback === "function" 在老的浏览器上会有问题,object 会当成个 function 。

可预测性

好的 API 具有可预测性,开发者可以根据例子推断它的用法。

Modernizr’s 特性检测 是个例子:

a) 它使用的属性名完全与 HTML5、CSS 概念和 API 相匹配

b) 每一个单独的检测一致地返回 true 或 false 值

  1. // 所有这些属性都返回 'true' 或 'false'
  2. Modernizr.geolocation
  3. Modernizr.localstorage
  4. Modernizr.webworkers
  5. Modernizr.canvas
  6. Modernizr.borderradius
  7. Modernizr.boxshadow
  8. Modernizr.flexbox

依赖于开发者已熟悉的概念也可以达到可预测的目的。

jQuery’s 选择器语法 就是一个显著的例子,CSS1-CSS3 的选择器可直接用于它的 DOM 选择器引擎。

  1. $("#grid") // Selects by ID
  2. $("ul.nav > li") // All LIs for the UL with class "nav"
  3. $("ul li:nth-child(2)") // Second item in each list

比例协调

好的 API 并不一定是小的 API,API 的体积大小要跟它的功能相称。

比如 Moment.js ,著名的日期解析和格式化的库,可以称之为均衡,它的 API 既简洁又功能明确。

像 Moment.js 这样特定功能的库,确保 API 的专注和小巧非常重要。

编写 API 文档

软件开发最艰难的任务之一是写文档,实际上每个人都恨写文档,怨声载道的是没有一个好用的文档工具。

以下是一些文档自动生成工具:

JavaScript API 设计准则的更多相关文章

  1. 出色的 JavaScript API 设计秘诀

    设计是一个很普遍的概念,一般是可以理解为为即将做的某件事先形成一个计划或框架. (牛津英语词典)中,设计是一种将艺术,体系,硬件或者更多的东西编织到一块的主线.软件设计,特别是作为软件设计的次类的AP ...

  2. 【Xamarin挖墙脚系列:Xamarin.Android的API设计准则】

    原文:[Xamarin挖墙脚系列:Xamarin.Android的API设计准则] 前言 楼主也是看着Xamarin的官方文档来的.基本也是照猫画虎.英语勉强凑合.翻译的不对的地方,大家多多指教.(这 ...

  3. JavaScript API 设计原则

    网+线下沙龙 | 移动APP模式创新:给你一个做APP的理由>> 好的 API 设计:在自描述的同时,达到抽象的目标. 设计良好的 API ,开发者可以快速上手,没必要经常抱着手册和文档, ...

  4. atitit.api设计 方法 指南 手册 v2 q929.docx

    atitit.api设计 方法 指南 手册 v2 q929.docx atitit.api设计原则与方法 1. 归一化(锤子钉子理论)1 1.1. 链式方法2 1.2. 规则5:建立返回值类型2 1. ...

  5. [转] 阿里研究员谷朴:API 设计最佳实践的思考

    API是软件系统的核心,而软件系统的复杂度Complexity是大规模软件系统能否成功最重要的因素.但复杂度Complexity并非某一个单独的问题能完全败坏的,而是在系统设计尤其是API设计层面很多 ...

  6. API 设计 POSIX File API

    小结: 1. https://mp.weixin.qq.com/s/qWrSyzJ54YEw8sLCxAEKlA API 设计最佳实践的思考 谷朴 阿里技术 昨天   阿里妹导读:API 是模块或者子 ...

  7. javascript的api设计原则

    前言 本篇博文来自一次公司内部的前端分享,从多个方面讨论了在设计接口时遵循的原则,总共包含了七个大块.系卤煮自己总结的一些经验和教训.本篇博文同时也参考了其他一些文章,相关地址会在后面贴出来.很难做到 ...

  8. JavaScript 的 API设计原则

    一.接口的流畅性 好的接口是流畅易懂的,他主要体现如下几个方面: 1.简单 操作某个元素的css属性,下面是原生的方法: document.querySelectorAll('#id').style. ...

  9. REST API设计指导——译自Microsoft REST API Guidelines(四)

    前言 前面我们说了,如果API的设计更规范更合理,在很大程度上能够提高联调的效率,降低沟通成本.那么什么是好的API设计?这里我们不得不提到REST API. 关于REST API的书籍很多,但是完整 ...

随机推荐

  1. Shell--nl命令

    nl命令在linux系统中用来计算文件中行号.nl 可以将输出的文件内容自动的加上行号!其默认的结果与 cat -n 有点不太一样, nl 可以将行号做比较多的显示设计,包括位数与是否自动补齐 0 等 ...

  2. Druid对比Hadoop

    Druid对比Hadoop Hadoop 向世界证明, 花费很少的钱实现典型的解决方案, 将数据保存在一般的商用机器的数据仓库里是可行的. 当人们将自己的数据保存在Hadoop, 他们发现两个问题   ...

  3. Solr删除数据

    步骤: 1.在Solr客户端左下方 Core Selector 中点选想要删除数据的索引库 2.点选Documents 3.右侧Document Type中点选XML 4.Document(s)中输入 ...

  4. CASE WHEN 的用法

    Case具有两种格式.简单Case函数和Case搜索函数. 简单Case函数 CASE sex WHEN '1' THEN '男' WHEN '2' THEN '女' ELSE '其他' END   ...

  5. 基于STC12C5A的MINI3216多功能点阵时钟

    代码地址如下:http://www.demodashi.com/demo/12862.html 基于STC12C5A的MINI3216多功能点阵时钟 硬件详解 PCB 硬件原理图 主控模块 max72 ...

  6. sql分组最大值相关

    房产表tf_estate_card,利润中心组profit_group_code,资产号main_assets_number,原值original_value 查出每个利润中心组的最大原值及其资产号 ...

  7. Android平台Native开发与JNI机制详解

    源文链接: http://mysuperbaby.iteye.com/blog/915425 一个Native Method就是一个Java调用非Java代码的接口.一个Native Method是这 ...

  8. linq-to-sql实现left join,group by,count

    linq-to-sql实现left join,group by,count 用linq-to-sql实现下面的sql语句: SELECT p.ParentId, COUNT(c.ChildId) FR ...

  9. 由"永恒之蓝"病毒而来的电脑知识科普

    永恒之蓝病毒事件: 继英国医院被攻击,随后在刚刚过去的5月12日晚上20点左右肆虐中国高校的WannaCry勒索事件,全国各地的高校学生纷纷反映,自己的电脑遭到病毒的攻击,文档被加密,壁纸遭到篡改,并 ...

  10. unity, 内存profile,ImageEffects Temp和Unity GI SystemTex RGBM

    最近用unity的Profiler对公司项目进行内存profile,发现一些问题,记录一下. 用Memory Area的Detailed View,用法见:http://docs.unity3d.co ...