开发者社区> 技术小美> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

认知维度与API的可用性评估

简介:
+关注继续查看

什么是认知维度

认知维度(也称作符号认知维度,Cognitive Dimensions or Cognitive Dimensions of Notations,简称CD)是一套关于符号标记、用户界面和编程语言的设计原则。
认知维度提供一种轻量级的方法来帮助分析设计,它包含了14条不同的维度来指导设计。

API的可用性

API和一般的产品一样,也需要具有良好的可用性。但API的不同之处在于,它一旦被发布出去,修改升级都比较困难。如果你变更了API,很多使用你API开发出来的产品就会受到影响,甚至无法正常工作。
所以我们在发布一套API之前,一定做了很多工作:严谨的设计、严格的测试等等。但是很少有人关注API的可用性,也没有行之有效的方法来指导API的可用性设计。
在微软,认知维度这个工具被拿来用在了API的可用性设计上,并对其进行适当的修改,形成了新的12个维度。

我们先来看看认知维度中的一个维度:Abstraction Level

Abstraction Level

Abstraction Level在CD中的描述为Abstraction Gradient,其解释为:

What are the minimum and maximum levels of abstraction exposed by the notation? Can details be encapsulated?

用到API设计中,可以转化为如下解释:

What are the minimum and maximum levels of abstraction exposed by the API, and what are the minimum and maximum levels usable by a targeted developer.

这个维度可理解为API所暴露出来的抽象层次是什么样的。如果抽象层次高,开发者使用起来将会感觉非常简单、方便,但缺点是不够灵活,缺乏自定义性;如果抽象层次低,则系统具有较大的灵活性,但却牺牲了部分易用性,开发者学习起来的代价也比较大。

我们通过一个具体的使用场景来理解这个概念,下面的代码片段在地图上创建了两个按钮,点击这两个按钮可分别将地图进行放大一级和缩小一级的操作:


  1. // 示例一  
  2. map.addControl(new BMap.StandardMapControl({type: BMAP_STD_MAP_CONTROL_ZOOM_ONLY}));  

创建按钮实际上是向地图容器里添加了新的DOM元素,并指定了一些样式,最后再绑定两个click事件,并在事件监听函数中调用map的zoomIn和zoomOut方法。
在地图API中,这些细节都被封装成为抽象的控件,对于开发者来说只要new一个这样的控件,同时指定一种外观类型(BMAP_STD_MAP_CONTROL_ZOOM_ONLY指仅包含放大和缩小按钮),再add到地图当中就好了。
可以看出这样的接口使用起来简单,接口数量少,也容易理解。

BMap.StandardMapControl的样式是预先定义好的,最多支持开发者选择几种不同的展现形式,如果开发者希望自定义按钮的样式该如何做?
这时可以选用抽象层次较低的API,请看下面示例:


  1. // 示例二  
  2. // 定义一个控件类  
  3. var ZoomControl = function(){  
  4.  
  5.   // 默认停靠位置和偏移量  
  6.   this.defaultAnchor = BMAP_ANCHOR_TOP_LEFT;  
  7.   this.defaultOffset = [10, 10];  
  8.  
  9. };  
  10. ZoomControl.prototype = new BMap.Control();  
  11. ZoomControl.prototype.initialize = function(map){  
  12.  
  13.   // 容器元素  
  14.   var container = document.createElement("div");  
  15.  
  16.   var divZoomIn = document.createElement("div");  
  17.   divZoomIn.appendChild(document.createTextNode("放大1级"));  
  18.  
  19.   var divZoomOut = document.createElement("div");  
  20.   divZoomOut.appendChild(document.createTextNode("缩小1级"));  
  21.  
  22.   container.appendChild(divZoomIn);  
  23.   container.appendChild(divZoomOut);  
  24.   // 设置样式  
  25.  
  26.   container.style.fontSize = "12px";  
  27.   divZoomIn.style.cursor = "pointer";  
  28.  
  29.   divZoomIn.style.border = "1px solid gray";  
  30.   divZoomIn.style.backgroundColor = "white";  
  31.  
  32.   divZoomOut.style.cursor = "pointer";  
  33.   divZoomOut.style.border = "1px solid gray";  
  34.  
  35.   divZoomOut.style.backgroundColor = "white";  
  36.   // 绑定事件  
  37.   divZoomIn.onclick = function(e){  
  38.     map.zoomIn();  
  39.   }  
  40.   divZoomOut.onclick = function(e){  
  41.     map.zoomOut();  
  42.   }  
  43.   // 添加DOM元素到地图中  
  44.   map.getContainer().appendChild(container);  
  45.   // 将DOM元素返回  
  46.   return container;  
  47. }  
  48.  
  49. // 创建控件  
  50. var myZoomCtrl = new ZoomControl();  
  51.  
  52. // 添加到地图当中  
  53. map.addControl(myZoomCtrl);  

在上面这个代码片段中,开发者使用了抽象层次较低的DOM API来实现元素创建、样式定义和事件绑定,同时还需要了解JavaScript的prototype继承机制以及地图API约定的继承控件BMap.Control的方法
(比如将new BMap.Control()赋值给prototype对象,initialize接口需要开发者自行实现等等)。
这样做极大地满足了开发者对灵活性的要求,但也要求开发者需要掌握更多的接口和内容(DOM编程、高级JavaScript编程、API使用方法等等),同时代码的数量和复杂程度也增大了。

再来看下面的例子,有个开发者希望创建一个地图,并且在地图上添加一些控件,使地图界面看起来和百度地图(http://map.baidu.com)的界面一样,那么以下代码可以实现这个需求:


  1. // 示例三  
  2.  
  3. // 依次创建导航控件、缩略地图和比例尺  
  4. map.addControl(new BMap.StandardMapControl());  
  5. map.addControl(new BMap.OverviewMapControl());  
  6. map.addControl(new BMap.ScaleControl());  

上面的代码通过多次添加不同的控件使该地图界面与百度地图保持一致。如果我们提供一个抽象层次更高的接口呢?可能会有如下的调用方式:


  1. // 示例四  
  2. map.addTypicalContorls();  

上面代码将之前的三句addControl换成了一句addTypicalContorls,添加多个控件的任务完全由addTypicalContorls方法内部去完成。

通过上面的示例我们看出,示例一和示例三的抽象程度属于中等,示例二的抽象程度低,示例四的抽象程度最高。
接口的抽象层次越高,开发者入门越容易,使用的接口数量少,但接口的灵活性低;反过来,接口的抽象层次越低,开发者需要了解的东西就越多,运用的接口数量也越多,代码写起来越复杂,但好处是接口灵活。

相信大部分开发者在刚接触一套新的API时,都喜欢通过少量的接口就能完成一些任务,这样学习成本低,开发起来起来也更轻松容易。
随着入门者逐渐使用API,他们当中就会出现一些具有一定开发经验且在灵活性上有很高要求的人,这就要求API能够提供一些层次较低、灵活程度较高的接口。
这些人也希望通过多个接口的组合来帮助自己实现功能。
在一套大型的API或者一个framework当中,需要考虑为不同的开发者提供不同抽象层次的接口,最大程度满足不同的需求。

Learning Style

Learning Style在CD中是没有的,微软将这个维度引入了进来,它的解释为:

What are the learning requirements posed by the API, and what are the learning styles available to a targeted develper.

开发者用API是来完成一些列的任务的,那么以下两种不同的完成方式确定了不同的学习风格。

  • 如果开发者完成任务需要少量的接口,并且每个接口之间相互依赖的程度较低,那么我们说API支持一种渐进的少量的学习方式。
    也就是说,开发者仅需要里了解与自己任务相关的接口,并且通过循序渐进的方式进行学习。

  • 如果开发者完成任务需要大量的接口,并且每个接口之间关联复杂,那么我们说API需要一种由上至下或者一种结构化的学习方式。
    也就是说,在用API完成任务之前,开发者需要了解API不同的接口以及它们之间的关联关系,同时可能还需要了解整个API的结构和其他一些新概念。

再拿百度地图API来说,如果开发者想创建一个简单的地图,他需要了解如下内容:

  1. 如何引用API(加载JavaScript)。
  2. 地图坐标和级别的概念。
  3. 知道通过BMap.Map类创建地图实例,并且通过centerAndZoom方法进行初始化。
  4. 知道通过BMap.Point类创建坐标实例。

可以看出,地图API的学习风格接近于第一种,属于渐进式的。开发者仅需要了解少量的接口和一些概念就能完成任务了。

不同的开发者可能喜欢不同的学习方式,比如有些人喜欢渐进式的,希望编写少量代码就能立刻看到成果,在此基础上逐步学习API中的其他接口。
另外一些开发人员希望在使用API之前能从一个较高的层次上了解其架构,喜欢从整体把握,接着再寻找解决任务的最佳方案。

在一套API中,两种不同的学习风格可以共存

Working Framework

What is the size of the conceptual chunk needed to work effectively.

该维度描述了开发者在使用API的过程中需要了解(keep track of)的信息量有多少,即开发者使用API时的工作框架是什么样的

比如,在通过BMap.Map类创建一个地图实例时,开发者可以指定地图所使用的坐标系统是经纬度还是墨卡托,一旦确定下来并创建地图后。这个坐标系统就是开发者需要了解的信息。
如果在后续开发中,需要添加一些标注、折线到地图上,开发者就要保证标注、折线的坐标与地图当前的坐标系统一致,否则程序就会出问题。

在API中,working framework的形式主要有以下三种:

  • 如果信息可通过API本身来表示,那么工作框架是本地(Local)式的。
  • 如果信息不通直接通过API来表示,但是可通过开发者的代码结构体现,这时工作框架是全局(global)式的。

  • 如果信息既不能通过API本身来表示,也不能在代码结构中体现,那么工作框架是系统(system)式的。

不同的开发者可以适应不同的工作框架模式,只要在他们的可接受范围内就行。

Work Step Unit

How much of a programming task must/can be completed in a single step.

这个维度描述了开发者完成特定任务所需要的步骤有多少,可以简称为工作单元

比如用户想在地图上添加自定义的标注,这个任务可通过以下三个步骤(不包含地图创建过程)完成:

  1. 创建一个图标对象。
  2. 创建一个标注,并设置其图标。
  3. 向地图添加标注

下面我们将详细描述每一个步骤开发者需要做哪些事情。这些事情包括一个代码块的编写或者创建了多少个类以及一些其他相关的工作。对于上面的例子,我们可进行如下描述:

 

步骤 工作内容
创建一个图标对象 创建一个BMap.Icon实例。传递一个图片路径、指定图片的宽和高、指定图片的锚点位置(即哪个点对应实际的坐标点)。相关工作都是和BMap.Icon相关的,没有涉及其他内容,这里仅需要一行代码。
创建一个标注,并设置其图标 创建一个BMap.Marker实例。传递一个坐标点,并指定使用的图标对象。仅需要一行代码
向地图添加标注 调用Map.addOverlay方法,将标注实例作为参数,添加到地图上。需要一行代码

 

经过上面一番描述,API的工作单元可通过如下方式描述:

  • 如果用户编写的代码包含在一个本地的代码块中,并且代码可以递增式的完成,那么工作单元可描述为本地递增式(local incremental)。
  • 如果用户编写的代码包含在多个代码块中,或者代码需要多个类来交互完成,那么工作单元可描述为并行式(parallel)。
  • 如果用户编写的代码既不是本地递增式也不是并行式,而是处于一种中间状态,那么工作单元可描述为功能式(functional)。

可以看出创建自定义标注的过程是本地的递增式的

Progressive Evaluation

To what extent can partially completed code be executed to obtain feedback on code behavior?

这个维度描述了开发者在编写每个任务的过程中,是否很容易的停下来并检查自己的进度完成情况。

  • 如果开发者在写完每一行代码就能够停下来评估进度,那么API支持单行代码级别的评估。
  • 如果开发者在写完两个或更多任务的代码后才能评估自己某一项任务的进度,那么API支持功能级别的评估。
  • 如果开发者需要同时使用多个类,并要求类之间的状态要保持某种一致性,这样才能查看自己的工作进度,那么该API支持并行模块级别的评估。

我们还拿上面添加自定义标注的任务来举例,它最接近第二种情况。因为在创建icon和marker实例后,开发者并不能看到目前已经编写的代码是否OK,他/她需要执行第三个步骤,
即把标注添加到地图上之后才能看到整体的代码是否正确。

Premature commitment

wiki上的解释为:

Are there strong constraints on the order with which tasks must be accomplished?
Are there decisions that must be made before all the necessary information is available?
Can those decisions be reversed or corrected later?

微软将其改为如下说法:

To what extend does a developer have to make decisions before all the needed information is available?

开发者在使用API完成任务之前,有时不得不提前思考一些问题,比如使用API的哪些接口完成任务,它们之间有何联系,调用顺序是怎样的等等。那么开发者的思考代价有多大呢?
是在写代码之前就能轻松得出结论,还是写了一些验证性的代码之后才可以,还是整个代码都写得差不多了才发现当初思考的方式并不正确,需要重头再来?

如果开发者在使用API过程中经常遇到最后一种情况,那么就认为该API含有过多的不合理的约束(我对premature commitment的理解)。
比如API约束了调用顺序,约束了接口之间的某种调用关系,并且这些约束是一般开发者不能提前思考清楚的。

比如地图API里可以给标注添加一个标题,当鼠标移到这个标注上时,就会显示这个标题,实际上,这是给标注的DOM容器元素增加了title属性。开发者在使用过程中可能会使用下面的写法:


  1. var marker = new BMap.Marker(map.getCenter());  // 创建一个标注对象,并给定一个坐标点  
  2.  
  3. marker.setTitle("百度大厦");            // 标注创建完了,给它加个标题  
  4. map.addOverlay(marker);             // 好了,添加到地图上  

当我们把鼠标移到标注上,“百度大厦”四个字并没有显示,哪出问题了?API文档那个上明明写了这个接口的作用呀?实际上,只有下面的写法才能实现:


  1. var marker = new BMap.Marker(map.getCenter());  // 创建一个标注对象,并给定一个坐标点  
  2.  
  3. map.addOverlay(marker);             // 先添加到地图上  
  4. marker.setTitle("百度大厦");            // 再给它加个标题  

其中原因很简单,由于标题是添加在DOM元素上的,那么在调用addOverlay方法之前,DOM元素并没有创建出来,setTitle发现DOM元素不存在就直接return了,也没有把这个title存起来,那么后来再添加到地图上也就看不到标题了。
这里,API就存在一个调用顺序上的约定,使用者可能在经历了失败时候才会知道这个约定,好在调整起来并不费力,至少代码没有白写。

一个好的API应该尽量减少这种不合理的约定。另外,如果想减少开发者的思考代价,一份结构清晰的文档是必不可少的。

Penetrability

How does the API facilitate exploration, analysis, and understanding of its components, and how does a targeted developer go about retrieving what is needed.

这个维度是指开发者需要理解API的底层实现细节到一个什么程度以及对这些细节的理解程度,也就是说API的透明程度(penetrability)如何。
比如,一个封装了很多底层接口的API需要使用者理解这些底层实现细节,否则API就不能正常工作。

API Elaboration

To what extend must th eAPI be adapted to meet th eneeds of a targeted developer?

开发者使用API来完成任务,其中一些任务仅仅通过调用几个API的现有接口就能完成,另一些任务可能需要继承API的一个类,并重载一些方法来完成,还有一些任务可能需要开发者自己编写接口的具体实现来完成。
这个维度描述了API暴露出来的扩展性,即API有哪些接口直接使用的,有哪些需要开发者自己继承扩展的,有哪些是开发者自行创建的。

当用户使用地图API时,有些功能可能是API中没有提供的,这时候就需要开发者自己写一些模块来处理(就像线上的百度地图)。

对于一些简单的任务,API要尽可能提供现成的接口供开发者使用。另有一部分开发者对接口的灵活性要求更高,希望通过扩展现有的API来实现自己的功能。API最好能同时满足这些不同的需求

API Viscosity

What are the barriers to change inherent in the API, and how much effort does a targeted developer need to expend to make a change.

这个维度描述了改变已写代码的容易程度,可简称为易改性。当用户发现自己代码运行错误或者发现选用的方法不对,修改起来是否足够容易呢?

这里的修改通常是指那些比较小的修改,但是一个小的修改可能会产生“多米诺”效应:一个地方的代码变动会导致另一个地方的代码需要调整,接着又会导致另一处……这就要求API在设计过程中模块划分要合理,同时处理好它们之间的耦合程度。

修改代码是不可避免的,这和我们思考行为有关系,我们会在大脑中勾画一个方案,并不断的进行调整。那些高手可直接在大脑中就想出一些中等复杂问题的解,一般人也能在大脑中直接处理一些简单的问题,而那些大型而又复杂的问题,估计没有人能够直接通过大脑就能想出解决办法。
当问题越大越复杂,API的易改性的作用就越明显。

Consistency

Once part of an API is learned, how much of the rest of it can be inferred?

一致性也是API可用性需要考虑的,我们经常会注意产品界面是否具有一致性,比如同一个网站的每个网页的菜单样式是否一样,图标风格是否一样,字体类型、颜色、大小是否一样等等。
Green在他的文章中也将这个因素描述为API的可猜测性,也就是说使用者了解了部分API的使用方法后,是否能很顺利的猜出API其他部分的使用方法。

在地图API中,本地搜索接口的使用方式为:


  1. var myLoc = new BMap.LocalSearch(map);  
  2.  
  3. myLoc.search("百度大厦");  

当用户了解它的使用方法后,可以很轻松的使用公交导航、驾车导航等接口。


  1. // 公交导航  
  2. var myTransit = new BMap.TransitRoute(map);  
  3. myTransit.search("百度大厦", "中关村");  
  4.  
  5. // 驾车导航  
  6. var myDrv = new BMap.DrivingRoute(map);  
  7. myDrv.search("百度大厦", "中关村");  
  8.  
  9. // 步行导航  
  10. var myWalk = new BMap.WalkingRoute(map);  
  11. myWalk.search("百度大厦", "西二旗");  

从上面可以看出,这些接口使用方法都非常类似,因此具有很高的一致性。

地图API中也存在一些一致性较差的问题,许多接口都要求提供offset属性,比如控件的位置偏移、标注、信息窗口的位置偏移。
当给控件添加偏移值时,采用的是下面的方法:


  1. var ctrl = new BMap.ScaleControl({offsetX: 10, offsetY: 10});  

我们通过offsetX和offsetY两个属性来描述这个偏移值,然而当我们获取控件偏移位置时,接口为:


  1. var offset = ctrl.getOffset();  
  2. alert(offset.x + ", " + offset.y);  

得到的偏移值使用了一个对象描述。那标注呢?


  1. var mkr = new BMap.Marker(map.getCenter(), {offset: [3, 4]});  

天哪,标注又是使用数组进行传递的。

在地图API中,我们使用了三种不同的方式来描述偏移值,这无疑增加了开发者的使用难度,也更容易出错。这个问题我们在API的后续升级中进行解决。

Role Expressiveness

How apparent is the relationship between each component and the program as a whole?

这个维度包含两方面的意思,那就是:说你所想,想你所说(原文是:say what you mean and mean what you say)。
一段容易阅读的代码(mean what you say)不一定很容易写出来(say what you mean)。所以在考虑这个维度时,不仅仅要考虑代码是否容易阅读,也应该考虑编写过程是否也同样容易。

在地图API初期,创建一个简单的导航控件的代码是这样的:


  1. var navCtrl = new BMap.NavigationControl({type: 1});  
  2. map.addControl(navCtrl);  

这段代码并不能完全表达出它的含义(cannot express its role),如果不借助文档,你几乎不可能知道type等于1意味着什么,最多你可能知道这里在指定控件的类型,但是1具体表示什么类型就不得而知了。
所以,升级之后的API做了如下调整:


  1. var navCtrl = new BMap.NavigationControl({type: BMAP_NAVIGATION_CONTROL_SMALL});  
  2. map.addControl(navCtrl);  

现在我们使用常量BMAP_NAVIGATION_CONTROL_SMALL来描述控件的类型,即使不看文档,也能知道这是个小型的导航控件。

Domain Correspondence

How clearly do the API components map to the domain? Are there any special tricks?

这个维度描述了API所提供的类、方法与那些概念上的对象是否吻合,也就是接口和问题领域的相关程度如何。
如果代码与问题领域相关程度高,那么开发者编写的过程就会轻松许多,反之,开发者就需要多花费一些时间和精力才能完成他们的工作。

举一个最简单的例子,我们打算遍历数组的每一个元素,并alert出来,那么我们可以用循环来实现:


  1. var a = [1, 2, 3];  
  2. for (var i = 0; i < a.length; i ++){  
  3.   alert(a[i]);  

而另一种可能的方法是:


  1. var a = [1, 2, 3];  
  2. a.each(function(x){alert(x)});  

或者干脆写成:


  1. var a = [1, 2, 3];  
  2. a.each(alert);  

程序员的目的是遍历数组,然而第一种方法出现的for循环结构以及相关的循环变量定义、数组长度判断等内容实际上是编程语言实现“遍历”功能所附加的一些概念。
显然这样的代码与问题领域的相关程度也比较低,而最后一种写法对于母语是英语的人来说简直就像说话一样简单。

参考资料

  1. Cognitive dimensions of notations, (http://en.wikipedia.org/wiki/Cognitive_dimensions_of_notations)

  2. Steven Clarke & Curtis Becker, Using the Cognitive Dimensions Framework to evaluate the usability of a class library, 2003.
  3. Green, T. R. G. & Petre, M. (1996) Usability analysis of visual programming environments: a ‘cognitive dimensions’ framework. J. Visual Languages and Computing, 7, 131-174.
  4. Steven Clarke & Visual Studio Usability Group, Microsoft Corporation, Describing and evaluating API usability at Microsoft.

 









本文转自百度技术51CTO博客,原文链接:http://blog.51cto.com/baidutech/747819,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Web API的CORS
Web API中进行跨域需要在请求头中加入允许跨域请求 Access-Control-Allow-Origin=*  上面代码代表允许所有跨域请求。当然也可以只允许某个站点进行跨域请求,只需将‘*’改为指定站定即可 Access-Control-Allow-Origin=“http://www.
781 0
Web API 版本控制的几种方式
http://www.troyhunt.com/2014/02/your-api-versioning-is-wrong-which-is.html 这篇文章写得很好,介绍了三种实现web api版本化的三种方式。
1170 0
+关注
6819
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载