API設計準則(轉) 轉

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的父親被禁止了, 它也應該變灰並且好像是它自己禁止的, 同時, 當其被激活的時候, 它會等待它的父親被激活), 但是必須在文檔裏寫清楚.

 

來源: http://lyxint.com/archives/256

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章