Six Characteristics of Good APIs
优良API的六个特征
An API is to the programmer what a GUI is to the end-user. The ‘P’ in API stands for “Programmer”, not “Program”, to highlight the fact that APIs are used by programmers, who are humans.
API于一个程序员正如GUI于使用者. API中的P表示程序员, 而不是程序, 以凸显出API是供程序员使用的, 而程序员也是人类.
In his Qt Quarterly 13 article about API design [doc.qt.nokia.com], Matthias tells us he believes that APIs should be minimal and complete, have clear and simple semantics, be intuitive, be easy to memorize, and lead to readable code.
在他的Qt Quarterly 13 article about API design[doc.qt.nokia.com]中, Matthias告诉我们他认为API应该小而全, 有清晰和简单的语义, 直观易记, 从而使代码易读.
- Be minimal: A minimal API is one that has as few public members per class and as few classes as possible. This makes it easier to understand, remember, debug, and change the API.
- 小: 一个小的API应该经可能的有较少的类和较少的类公共成员. 这让它易于理解, 记忆, 调试和更改
- Be complete: A complete API means the expected functionality should be there. This can conflict with keeping it minimal. Also, if a member function is in the wrong class, many potential users of the function won’t find it.
- 全: 一个全的API, 意味着预期的功能都具有. 这也许会和小冲突. 但是, 如果一个成员函数存在与一个错误的类里面, 许多该函数的潜在用户很难发现他们.
- Have clear and simple semantics: As with other design work, you should apply the principle of least surprise. Make common tasks easy. Rare tasks should be possible but not the focus. Solve the specific problem; don’t make the solution overly general when this is not needed. (For example, QMimeSourceFactory in Qt 3 could have been called QImageLoader and have a different API.)
- 清晰和简单的语义: 就像和其他的设计工作一样, 你应该运用最小惊奇定理. 让普通的任务容易完成. 稀有的任务也能完成但不是关注点. 解决特定的问题, 没必要的话不要让解决方法过于普适. (例如, Qt 3中的QMimeSourceFactory应该被定义为QImageLoader, 并且有不同的API)
- Be intuitive: As with anything else on a computer, an API should be intuitive. Different experience and background leads to different perceptions on what is intuitive and what isn’t. An API is intuitive if a semi-experienced user gets away without reading the documentation, and if a programmer who doesn’t know the API can understand code written using it.
- 直观: 与计算机里的其他东西一样, API应该是直观的. 不同的经验和背景导致对直观的不同理解. 如果一个稍有经验的人不需要查阅文档就能理解API, 或者是一个不了解API的程序员能明白用这个API写的代码, 那么这个API就是直观的.
- Be easy to memorize: To make the API easy to remember, choose a consistent and precise naming convention. Use recognizable patterns and concepts, and avoid abbreviations.
- 易记: 为了让API易记, 选择一个统一且精确的命名规范. 使用易记的模式和概念, 避免缩写
- Lead to readable code: Code is written once, but read (and debugged and changed) many times. Readable code may sometimes take longer to write, but saves time throughout the product’s life cycle.
- 使代码易读: 代码只用写一次, 但是会被阅读(或者调试, 修改)很多次. 易读的代码需要较长的时间去书写, 但是在产品的生命周期里节省了可观的时间
Finally, keep in mind that different kinds of users will use different parts of the API. While simply using an instance of a Qt class should be intuitive, it’s reasonable to expect the user to read the documentation before attempting to subclass it.
最后, 牢记在心, 不同的用户会使用API的不同部分. 虽然简单的使用一个Qt类的实例是很直观的, 但是用户应该在继承类之前阅读文档.
Static Polymorphism
静态多态性
Similar classes should have a similar API. This can be done using inheritance where it makes sense — that is, when run-time polymorphism is used. But polymorphism also happens at design time. For example, if you exchange a QProgressBar with a QSlider, or a QString with a QByteArray, you’ll find that the similarity of APIs makes this replacement very easy. This is what we call “static polymorphism”.
相似的类应该具有相似的API. 这可以通过在需要的地方使用继承来做到 — 那就是说, 当用到运行时多态的时候. 但是多态也会出现在设计的时候. 例如, 如果一个QProgressBar去替换QSlider, 或者一个QString去替换QByteArray, 你会发现相似的API会让替换很容易. 这就是我们说的静态多态性.
Static polymorphism also makes it easier to memorize APIs and programming patterns. As a consequence, a similar API for a set of related classes is sometimes better than perfect individual APIs for each class.
静态多态性也让API和编程模式易记. 这样的结果就是, 一个相似的API对应着一些相关类的集合, 有些时候比每个类的完美的独立的API好得多.
In general, in Qt, we prefer to rely on static polymorphism than on actual inheritance when there’s no compelling reason to do otherwise. This keeps the number of public classes in Qt down and makes it easier for new Qt users to find their way around in the documentation.
通常来说, 在Qt里, 继承上我们选择依赖静态多态性, 当没有无法控制的原因让我们另外换一种做法的时候. 这让Qt里类的数量减少了, 并且让新的Qt用户更容易在文档里找到他们.
Good: QDialogButtonBox and QMessageBox have similar APIs for dealing with buttons (addButton(), setStandardButtons(), etc.), without publicly inheriting from some “QAbstractButtonBox” class.
好: QDialogButtonBox和QMessageBox在处理按钮时有相似的API(addButton(), setStandardButtons(), 等等), 并没有从类似于“QAbstractButtonBox”这样的类继承而来.
Bad: QAbstractSocket is inherited both by QTcpSocket and QUdpSocket, two classes with very different modes of interaction. Nobody seems to have ever used (or been able to use) a QAbstractSocket pointer in a generic and useful way.
坏: QAbstractSocket继承于QTcpSocket和QUdpSocket, 这两个类有着非常不同的交互. 貌似没人能通用且有效的使用QAbstractSocket指针.
Dubious: QBoxLayout is the base class of QHBoxLayout and QVBoxLayout. Advantage: Can use a QBoxLayout and call setOrientation() in a toolbar to make it horizontal/vertical. Disadvantages: One extra class, and possibility for users to write ((QBoxLayout *)hbox)->setOrientation(Qt::Vertical), which makes little sense.
不好不坏的: QBoxLayout是QHBoxLayout和QVBoxLayout的基类. 好处: 可以使用 QBoxLayout 并且在toolbar中调用tsetOrientation()来让它横向/竖向. 坏处: 多了一个类, 可能用户会写出((QBoxLayout *)hbox)->setOrientation(Qt::Vertical)这样的代码, 虽然有一些意义.
Property-Based APIs
基于属性的API
Newer Qt classes tend to have a “property-based API”. E.g.:
新的Qt类倾向于”基于属性的API”, 例如:
1
2
3
4
QTimer timer; timer.setInterval(1000); timer.setSingleShot(true); timer.start();
By property, we mean any conceptual attribute that’s part of the object’s state — whether or not it’s an actual Q_PROPERTY. When practicable, users should be allowed to set the properties in any order; i.e., the properties should be orthogonal. For example, the preceding code could be written.
提到属性, 我们是指任何属于对象状态的概念性属性 – 不管其是不是Q_PROPERTY. 当可行的时候, 用户应该能以任何顺序设置属性. 那就是说, 属性是正交的. 例如, 前面的代码也可以这样:
1
2
3
4
QTimer timer; timer.setSingleShot(true); timer.setInterval(1000); timer.start();
For convenience, we can also write timer.start(1000).
方便起见, 我们也可以写 timer.start(1000);
Similarly, for QRegExp, we have
类似的, 对于QRegExp, 我们有:
1
2
3
4
QRegExp regExp; regExp.setCaseSensitive(Qt::CaseInsensitive); regExp.setPattern("***.*"); regExp.setPatternSyntax(Qt::WildcardSyntax);
To implement this type of API, it pays off to construct the underlying object lazily. E.g. in QRegExp’s case, it would be premature to compile the “***.*” pattern in setPattern() without knowing what the pattern syntax will be.
为了实现这种类型的API, 延迟创建底层对象是值得的. 比如, 在QRegExp的例子里, 可以提前在Pattern里编译”***.*”模式, 而不需要知道模式语法是什么.
Properties often cascade; in that case, we must proceed carefully. Consider the “default icon size” provided by the current style vs. the “iconSize” property of QToolButton:
属性是可以层叠的. 在这种案例里, 我们必须小心处理. 设想当前样式提供的”默认图标大小”和QToolButton的”iconSize”属性:
1
2
3
4
5
6
7
toolButton->iconSize(); // returns the default for the current style toolButton->setStyle(otherStyle); toolButton->iconSize(); // returns the default for otherStyle toolButton->setIconSize(QSize(52, 52)); toolButton->iconSize(); // returns (52, 52) toolButton->setStyle(yetAnotherStyle); toolButton->iconSize(); // returns (52, 52)
Notice that once we set iconSize, it stays set; changing the current style doesn’t change a thing. This is good. Sometimes, it’s useful to be able to reset a property. Then there are two approaches:
注意一但我们设置了iconSize, 他就一直被设置了. 改变当前样式对它并没有影响. 这很好. 有时候, 重置一个属性是非常有用的. 有两种方法可以做到:
- pass a special value (such as QSize(), -1, or Qt::Alignment(0)) to mean “reset”
- 传递一个特殊的值(例如QSize(), -1, 或者Qt::Alignment(0))来表示重置
- have an explicit resetFoo() or unsetFoo() function
- 有一个显示的resetFoo()或者unsetFoo()的函数
For iconSize, it would be enough to make QSize() (i.e., QSize(-1, -1)) mean “reset”.
对于iconSize, 用QSize() (例如QSize(-1,-1)) 来重置就足够了.
In some cases, getters return something different than what was set. E.g. if you call widget->setEnabled(true), you might still get widget->isEnabled() return false, if the parent is disabled. This is OK, because that’s usually what we want to check (a widget whose parent is disabled should be grayed out too and behave as if it were disabled itself, at the same time as it remembers that deep inside, it really is “enabled” and waiting for its parent to become enabled again), but must be documented properly.
在有些案例中, getter的返回和设置的值不同. 比如, 如果你调用widget->setEnabled(true), 你仍然可能会从widget->isEnabled()那里得到false, 如果widget的父亲是被禁用了的. 这没什么, 因为我们通常想去检查(一个widget的父亲被禁止了, 它也应该变灰并且好像是它自己禁止的, 同时, 当其被激活的时候, 它会等待它的父亲被激活), 但是必须在文档里写清楚.