API设计小手册(上) — 什么是好的API

给想写类库的人。本文是vrcats按照一个Qt开发工程师写的《A Little Man l of API Design》翻译而成的。其中总结了多年Qt开发中关于API设计的一些经验和教训,提炼出了一系列关于API设计和面向对象类库设计的原则和方法论。水平很高,门槛不高,是vrcats目前看到的最好的关于C++API设计资料。
本文并非严格翻译。有许多段落思维太跳跃了不搭界,作了删节处理。有些例子不恰当的也作了一些修改,也加入了一点个人对API设计的理解。译者对于API设计其实也是初哥,相信国内做API设计的也不多,从这方面资料的量就看得出来。翻译得虽然有点糙,总的思想不会错。全文万字左右,不长,大家慢慢咀嚼。发现错误回帖告诉我,我会及时更正。转贴请注明原始来源,译者和转自CuteQt。

原著:http://twiki-nokia.troll.no/twiki/p /Development/Training/api-design.pdf

目录:

* 简介
* 好的API长啥样
* 设计流程
* 设计原则

简介

API是应用编程接口的缩写。说白了就是开放给编程者的一堆符号,这样他们才能使用你的库。API设计是库设计中最难搞的一步,会影响到上层应用程序的结构。正如丹尼尔杰克逊大叔说的,软件就跟砍竹子是一样一样的,都那么抽象。你要找对了口儿就势如破竹,那叫顺水行舟一路狂奔,加新功能也不需要怎么大改。你要是一开始设计错了,那就两步一个坎三步一个疖子,等着无穷的惊讶吧。

本手册来自多年Qt开发的经验。设计的时候要仔细考虑API设计,同时兼顾实现难度和性能。甭管你是设计公用API还是本地库,这些规则都挺有用的。

好的API长啥样?

这个问题太主观。在跟一些大牛研究之后,大家一致认为好的API要有如下特点:

* 好学好记
* 看了就知道是干啥的
* 不容易用错
* 容易扩展升级
* 完整

有俩特点这里没写,“简洁”和“一致”,因为这俩是废话。一套不一致的API不可能易学易用,也不可能很容易扩展。简洁当然应该是我们追求的目标,但是简洁的最终目的也是为了强化以上几个特点。

但是设计API的时候,一定要注意一致性。在系统设计中,一致性是非常重要的,尤其是复杂的系统。所有的想法都要围绕一个原则,采用一套方法。如果你手头已经有了一套很好的库,你想扩展它的时候一定要尽量学着原来的风格,这是成功的捷径。

好学好记

让API好学好记可不容易。命名,符号,概念和推测在这里都很重要。同一概念应该有同样的名字,不同的概念必须有不同的名字。
简洁的API好记,因为短。一致的API好记,因为能够联想推测。
API不仅仅是类和方法的名字那么简单,还包含其中隐含的语义。这个语义应该是简单明确的,让用户能够猜到。
有些API门槛很高,用起来很麻烦,要写一大堆的代码才能工作,这样就很难用。比如自己定义一个widget,3D界面,搞一个model,这都很让人头疼。如果API设计得好,用户应该能很容易写出一个”Hello World”,然后从这里开始慢慢扩展他们的知识。

比如说QPushButton就是个容易学的API代表。只要生成一个对象,甚至都不用调用show(),就可以完成一个完整的”Hello World”:

int main() {     QPushButton button("Hello world");     return QApplication::run(); }

有时候,提供一些便利API有利于用户记忆和使用你的库。比如你有一个API叫insertItem(int, Item),一般就应该提供一个addItem(Item)来方便用户使用。

看了就知道是干啥的

有的计算机语言容易理解,但也有不好理解的,比如APL语言,简直就是数学公式的堆砌。Perl的名声也不好,看Perl程序简直就像在读密码本。

应用程序只写一次,但是是要被读很多次的。程序易读则易写文档易维护,也会比较少有错误,因为错误会比较明显容易发现。

比如在Qt3里,QSlider可以这样初始化:

slider=new QSlider(8,128,1,6,Qt::Vertical,0,"volume");

在Qt4里,一般这样写:

slider=new QSlider(Qt::Vertical); slider->setRange(8,128); slider->setVal(6); slider->setObjectName("volume");

这样比较容易读,而且容易发现错误:设置值6对于slider越界了。

可读性强的代码来自于适当的抽象。不要隐藏重要的信息,也不要让用户提供无关的信息。

比如Qt Jambi的一段程序:

QGridLayout layout=new QGridLayout; layout.addWidget(slider,0,0); layout.addWidget(spinBox,0,1); layout.addWidget(resetButton,2,1); layout.setRowStretch(1,1); setLayout(layout);

相比之下Java Swing的代码就傻多了:

GridBagLayout layout=new GridBagLayout(); GridBagConstraints constraint=new GridBagConstraints(); constraint.fill=GridBagConstraints.HORIZONTAL; constraint.insets=new Insets(10,10,10,0); constraint.weightx=1; layout.setConstraints(slider,constraint); constraint.gridwidth=GridBagConstraints.REMAINDER; constraint.insets=new Insets(10,5,10,10); constraint.weightx=0; layout.setConstraints(spinner,constraint); constraint.anchor=GridBagConstraints.SOUTHEAST; constraint.fill=GridBagConstraints.REMAINDER; constraint.insets=new Insets(10,10,10,10); constraint.weighty=1; layout.setConstraints(resetButton, constraint); JPanel panel=new JPanel(layout); panel.add(slider); panel.add(spinner); panel.add(resetButton);

知道为什么Qt比Java好了吧。

不容易用错

好的API会引导用户正确使用,甚至引导用户采取更好的编程风格,想用错都难。好的API不应该限制用户以某些特定的顺序和方式来调用API。
比如我们看如下三种语言的语法,html,tex和latex:

html: the <b>goto <u>label</u></b> statement tex: the {\bf goto \underline{label}} statement latex: the \texbf{goto \underline{label}} statement

html的语法明显重复,而且容易搞错顺序,造成错嵌套。而tex的语法很容易忘记括号,造成错误蔓延。latex就解决了这个问题,使用比较长的命令,用户不太容易打错。

设计API其实和设计脚本语言很类似。你应当把API本身看作一种语言,而不是语言的扩展。以下的C++代码打印了上文的html文档:

stream.writeCharacters("the"); stream.writeStartElement("b"); stream.writeCharacters("goto "); stream.writeStartElement("i"); stream.writeCharacters("label"); stream.writeEndElement("i"); stream.writeEndElement("b"); stream.writeCharacters(" statement");

方便一点,你可以让C++编译器检测是否有错误嵌套:

stream.write(Text("the ")+Element("b",Text("goto ")+Element("u","label"))+Text(" statement"));

这种问题其实很常见。比如在配置文件的使用中,你可能会写这样的代码:

QSettings settings; settings.beginGroup("mainwindow"); settings.setVal("size",win->size()); settings.setVal("fullScreen",win->isFullScreen()); settings.endGroup();

还有一个来自脚本语言的教训。html开发者曾经试图用<em>来代替<i>,用<strong>来代替<b>,但是用户根本不买帐。因为敲<i>,<b>容易得多,而且意义更加明确。如果真的要修改成语义明确的符号,他们也该改成<span>之类的,这样不上不下的API设计让人很不爽。

在Qt3中大家经常把qPushButton, QLabel, QLineEdit的ObjectName和parent两个参数顺序弄错。你如果写成:

button=new QPushButton(this, "Hello");

编译能够通过,可是没有文字,因为参数顺序错了。所以在Qt4里改为:

button=new QPushButton(this); button->setObjectName("Hello");

这样就明确多了。

最后,去掉多余的部分能够避免用户错用API。比如addItem(Item)就比insertItem(int, Item)更不容易用错。

容易扩展

库一直在成长。新的类加入,旧的类获得新的方法,新的枚举值。这都是API设计需要考虑的问题。在原始API设计的时候,更应该考虑到二进制兼容的问题。
Qt2 的QStyle是一个扩展性极差的反面典型。它定义了一系列的虚函数画widget,用户想要在不破坏二进制兼容的情况下实现自己的style几乎不可能。Qt3注意到了这个问题,把style变成了基于枚举的,用户只要增加枚举元素就可以轻松定义新的style了。

有完整性

API必须提供用户需要的所有功能。这通常是困难的,一般来说,API可能不直接提供所有的功能,而是提供基础的功能,高级功能由用户自行设计实现,如s classing。

完整性也是与时俱进的。使用中可能会不断出现用户需求。但是至少一开始要瞄准正确的方向,每一个新的需求都是往正确方向前进的一步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值