Design is a very common concept, which can generally be understood as forming a plan or framework for something to be done. (Oxford English Dictionary), design is a thread that weaves together art, systems, hardware, or more. Software design, especially API design as a subcategory of software design, is the same. But API design often pays little attention to software development, because writing code for other programmers is secondary to application UI design and end-user experience.
But API design, as the public interface provided in the library we write ourselves, can show some features and functions of our library to developers who call our code, so API design is as important as UI design. In fact, both are fundamental ways in which applications can provide a better user experience. Application UI occupies a very important position in user UX, and application API is the developer's UX. Therefore, application API design should be given the same level of consideration and attention as the interface we provide to our users. Just as we care about the functionality, simplicity, and beauty of our UI, we should also evaluate the functionality, simplicity, and beauty of our APIs and code!
API Design - The content of JavaScript API design presents unique challenges to all developers, regardless of whether you are developing a public library or an internal library. The dynamic nature of JavaScript, the anonymity of library users, and the ambiguity of requirements all present a daunting challenge to API designers. However, there are no shortcuts to a good API design, but it is possible to extract some design principles from some of the most popular modern JavaScript libraries!
API Design: The Struggle between Angels and Demons
Poor design in your JavaScript API will bring high costs to the developers using your API and to you. Poor design leads to waste. Developers using your API will waste time trying to figure out your interface, and API developers will waste time dealing with increasing demand and solving user confusion. However, when almost all APIs were originally developed, they were designed to extract the same functions, facilitate calls, and save time. But a poorly designed API will make your library users and you wonder, do these libraries really save time?
Excellent API design, on the one hand, completes the goal of extraction, and also achieves self-description. When an API is well designed, users can get their work done quickly and intuitively, without having to constantly browse documentation or visit support or answer websites. You can also save library developers time by encapsulating some features that would take them a lot of time to develop themselves. Good design not only saves developers time, it also makes them look smarter and more responsible. Also helping your users look smart and capable will make you look even more awesome!
For javascript, API design is particularly important
No matter what programming language or framework, API design is important. The importance of API design for JavaScript is higher than that of many other languages. First, as a dynamic and late-bound language, JavaScript does not have a compiler that can implement a safety net or detection unit function, so JavaScript cannot find errors in your code. Linting or validation frameworks like JSLint and JSHint can help us. The functions of these frameworks can point out some common errors in javascript, but they cannot find javascript errors when we use APIs.
It all depends on you, you can develop a well-designed API that helps your users fall into the proverbial "success pit", which means your library is comfortable for developers and familiarity, while also providing positive reinforcement and building confidence as developers interact with your code.
The best example of "falling into the pit of success" is the use of jQuery to obtain DOM elements through CSS selector syntax. For example, if I want to get all article elements with a class name, I can do this using jQuery:
$("article.blogPost").fadeIn();
The selector article.blogPost uses exactly the same syntax as shown below. This is no accident!
article.blogPost { border-radius: 10px; background-color: salmon; box-shadow: 0px 0px 10px 2px #ccc; }
jQuery的选择器引擎被设计为了使我和其他开发者能够使我对CSS选择器的理解和它的引擎进行交互。结果可想而知,如果jQuery需要我用一种新的,为特定目的形成的语法,我将失去快速,明显和高效。
我们可以获得灵感从这些框架中,如jQuery,或者其他框架,并应用这些灵感到我们的设计中。然而,获得灵感并不是抄袭,有个度的问题,任何设计过API的人如果是仅仅的基于别人的想法,不管好与坏,他都将继承。如果我们将在好的javascript中获得的准则运用到其他领域中,我们能开发拥有好的API的框架,这些API设计能被运用在任何情况下。
出色的Javascript APIs设计秘诀
虽然软件不具有与绘画或建筑类似的视觉评价标准,我们仍倾向于使用与物理实体一样的形容词来描述软件质量。例如,使用“优雅的”与“漂亮的”来赞美软件并不罕见。如果用与物理实体相似的形容词描述软件接口是合理的话,那么当然也可以使用与之相同的原则来评价软件设计。
在本节,将四个来自艺术领域的流行设计原则扩展至API设计中:
对每一个原则,将列出一到多个实例来说明,这些例子表明流行的Javascript库API设计是怎样遵循这些原则的。
原则1:一致性&协调性
在艺术作品中,一致性是一个作品背后不可缺少的观念,或者说设计者如何把一些事物组成连贯的一个整体。协调性,从另一方面来说,是一个作品相似元素的布局,这会在考虑整体时产生一种简洁的感觉。
对于API的设计者,这些原则可以通过在类库使用类似的和(或者)统一的元素来实现。就拿Kendo UI来说吧,一个创建富web应用程序的javascript框架。Kendo UI提供了一系列的UI控件和工具,这些都可以通过一个简单的语法初始化。比如,如果我想从一个无序列表创建一个树形控件(TreeView),我只需调用以下方法:
$("ul.tree").kendoTreeView({ /* Configuration goes here */ });
Kendo UI树形组件
如果我想通过一个列表创建一个面板PanelBar,我只需稍微改成不同的调用方法.
$("ul.panel").kendoPanelBar({ /* Configuration goes here */ });
Kendo UI 面板组件
Kendo UI 对所有组件使用一致的kendoX语法,促进整体的协调。更重要的,这样的设计依赖jQuery对象为DOM元素封装了统一的一层,使设计有利于所有熟悉jQuery开发者。数百万开发者使用类似的“土语”(jQuery语法),Kendo UI可以顺利地跨库使用。
另一个协调的案例是Backbone的[object].extend语法创建对象,继承和扩展Backbone的Models,Views,Collections和Routers的功能。用如下代码就可以创建一个Backbone Model,带有Backbone的完整支持,也可以自定义我需要的功能:
var Book = Backbone.Model.extend({ initialize: function() { ... }, author: function() { ... }, pubDate: function() { ... }, });
统一和协调的目的是让API新手感觉熟悉和舒服。通过虽然功能不同,但是语法相同或相似,使API变得熟悉,大大减轻了开发者使用新工具的负担。
原则 2 :平衡
下一条原则是平衡,组织元素时不会让某个部分过于重量级而盖过其它部分,使用时不稳定。艺术作品里,平衡就是视觉权重。即使不对称,作品中仍能感觉到不对称下的平衡,因为它遵循某种模式。上下文中的API设计的平衡,我特指代码的视觉权重和可预测性(看得出功能)。
平衡的API让人觉得其组成部分属于彼此,他们行为相同,或互补地完成一个目标。通过扩展,APIs也可以感觉平衡,它们允许开发人员简单的预测其他API并使用。如Modernizr的属性测试,它们的平衡性在两个方面,a)属性名对应HTML5和CSS术语和API名称,b)每个属性测试统一地返回true或false值。
// All of these properties will be 'true' or 'false' for a given browser Modernizr.geolocation Modernizr.localstorage Modernizr.webworkers Modernizr.canvas Modernizr.borderradius Modernizr.boxshadow Modernizr.flexbox
访问一个单一的属性来告诉开发者需要了解到的相关属性,以便通过它访问每一个其他属性,一个高质量API的强大之处就在于它的简单。平衡性也保证了我写和Modernizr交互的代码在每次读写时具有相同的视觉加权。如何在我使用和访问API时看起来和感觉上一样,而不顾我的惯例。另一方面,如果Modernizr添加了一个polyfill Canvas的API,不仅仅是类库的视觉加权受到新API的影响,Modernizr的范围和用途也将大大扩大,并且我在和API交互时可预测性也受到了限制。
达到平衡的另一种方式是通过依靠开发人员对概念的熟悉获得可预测性的结果。一个典型的例子就是jQuery's selector syntax(jquery选择器的语法),它映射css1-3的选择器到自己的DOM选择器引擎:
$("#grid") // Selects by ID $("ul.nav > li") // All LIs for the UL with class "nav" $("ul li:nth-child(2)") // Second item in each list
通过使用一个熟悉的概念并且映射到自己的类库,jquery避免了新的选择器语法,同事也创建了一个机制让新用户通过一个可预测的API快速的把类库应用到生产.。
原则 3: 相称性
接下来的原则是相称性,它是用来衡量一个作品中元素的大小和数量的。与其说一个好的API是一个小的api,相称性是相对于用途的大小。一个相称的API它的API表面和它的能力范围相匹配。
例如,Moment.js,一个流行的日期转换和格式化类库,可以把它视为具有相称性,因为它的API表层是紧凑的,它和类库的目的明确的匹配。Moment.js用于处理日期,它的API提供了便利的功能用来处理javascript Date对象:
moment().format('dddd'); moment().startOf('hour').fromNow();
对于一个有针对性的类库,像Moment.js,保持API的专注和简单是非常重要的。对于更大和更广阔的类库,API的大小应当能够反映出类库自身的能力。
拿Underscore来说,作为一个多种用途功效的库,它提供大量便利的函数,这些被设计的函数是用来帮助开发者处理javascript集合,数组,函数和对象。它的API量远远超过像Moment.js这样的库,但是Underscore也是成比例的,因为库中每个函数都有自己的功效目的。考虑下面的例子,前两个例子用Underscore来处理数组,最后一个来处理字符串。
_.each(["Todd", "Burke", "Derick"], function(name){ alert(name); }); _.map([1, 2, 3], function(num){ return num * 3; }); _.isNumber("ten"); // False
当一个库逐渐成长的过程中,维持比例的挑战变的更加具有严峻。为了确保添加进库的每个功能和函数都能加强库的目的,需要更多的考虑投入。对于一个大的库像kendo UI,易扩展性的目的并不是意味着我们需要往库中添加每个特性。对于一个像kendo一样大的库,功能对象和特性应该证明它们的价值才能被库包含。例如, Kendo UI's JavaScript 基于DataSource, 它能够被用来查询和处理远程数据。
var dataSource = new kendo.data.DataSource({ transport: { read: { url: "http://search.twitter.com/search.json", dataType: "jsonp", data: { q: "API Design" } } }, schema: { data: "results" } });
初看第一眼,它好像一个习以为常的数据源,感觉超出了库本身的基本目的。然而今天网站的装饰都需要动态数据的支持。数据源的引入允许Kendo UI可以使用一个稳定,并舒适的范式在整个库范围内来解决远程数据。
让一个API转变为一个名符其实的javascript垃圾抽屉,对于一个库的扩展这是危险的,但对于库来说,这也不是唯一的危险。掉入一个不让你的API伴随着库的成长圈套,或者由于某些人为原因,限制你库的大小,这些同样都是危险的!
不处理API增长最好的一个例子是jQuery的 jQuery or $ function。和我一样有成千上万的开发者喜欢jQurey, 但它的门户方法是有点乱的,从DOM选择到在jQuery对象中包含DOM元素,这个方法提供了超过11个独立超负荷选择方式。
就大部分而言,有些不是十分相关的特性被硬塞进同一个API。从全局看,jQuery是一个大的库并且能被认为库比例是合理的。另一方面,当我们尝试将一个功能硬塞进一个单一接口并且不考虑库比例,jQuery方法也可以实现这样的功能。
如果你发现你正在将一个不相干的特性强塞进已经存在的方法,或者正在想法设法使一个并不适合API的函数的添加合理化,你需要做的改变是松开皮带并且让库呼吸。你的用户在调用一个新的可以自我描述名字的函数时,将会更加节省时间,并且不会给另一个已经存在的方法添加负担。
原则 4: 强调性
在艺术作品中,强调是利用对比来使作品中某一方面脱颖而出形成一个焦点。在许多API中,焦点可能是一个通道或者类库主要方法的锚点。另外一个关于强调性的例子可以参考“链接”方式或者fluent API,它通过增加强调性效果突出了类库中心对象。jquery倾向于从许多功能演示中的强调这个对象:
$('ul.first').find('.overdue') .css('background-color','red') .end() .find('.due-soon') .css('background-color', 'yellow');
对于许多现代的类库,另一个关于强调的例子是可扩展性:类库创建者没有提供的那部分,会为你提供一个工具你可以自己完成相关扩展。
一个典型的例子可以参考jQuery'sfn(pronounced “effin”) namespace, 一般的扩展点可以通过数不清的插件和补充的类库来完成:
(function($) { $.fn.kittehfy = function() { return this.each(function(idx, el) { var width = el.width, height = el.height; var src= "http://placekitten.com/"; el.src= src + width + "/" + height; }); }; })(jQuery);
另一个扩展性的例子是Backbone的“extend”的函数,我们已经在本文中看到过:
var DocumentRow = Backbone.View.extend({ tagName: "li", className: "row", events: { "click .icon": "open", "click .button.edit": "openEditDialog" }, render: function() { ... } });
Extensibility is an important aspect because it makes us aware of the fact that the existing class library does not mean that everything is perfect, and it also encourages us to extend the class library that suits us. When class libraries support extensions, they not only open up new uses, but also enable countless developers to benefit from general uses. One of the best examples is the Backbone.Marionette framework, a class library that extends Backbone and whose goal is to "simplify the structure of large JavaScript applications." Without library extensions like Backbone, libraries like Marionette would become very complex or even impossible to implement.
API design: not just for library code writers
If you are not a JavaScript library writer, but a JavaScript application developer or library implementer, you may feel that the principles in this article do not apply to you. After all, when most of us hear "API", we tend to think of third-party libraries, just like the example I used in this article
The fact is that an API, as its definition states, is nothing more than an interface that provides isolated functionality for others to take advantage of. Now, let me use an old saying to emphasize an important point: write modular JS code for practicality, the number of times it is used does not matter.
Like the libraries referenced in this article, you can expose your JavaScript code to others. Even if the users of your code are a small group or internal team—even if you build a private library of your own—you don't have to think about the API design principles in this article and the implementation of those principles in the same way that the author of a public library does. The advantage of leveraging API design is that even if it is only for one user, you still need to design as if it were for millions of users.
Because API design represents user experience for developers, it is just as important as UI design for end users. Just as we can develop a good UI by learning some principles and referring to some good and bad examples, we can also learn better API design in the same way. Applying the four principles mentioned in this article, as well as others you discover on your own, can help you build excellent APIs and provide users with a good experience.