Qt圖形介面庫的設計在介面API設計中算是十分優秀的,前幾天在coolShell上看到了一篇強烈推薦的API設計原則文章,仔細研究了下,文章地址如下:
http://developer.qt.nokia.com/wiki/API_Design_Principles
簡單記錄了一下重點和痛點,以備忘。
High point:
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也相似,這樣就有了好的替換性。
Static polymorphism also makes it easier to memorize APIs and programming patterns.
這樣帶來的好處還有易於記憶和設計介面。
Property-Based APIs
這裡所有的類中的屬性應該盡量可供使用者佈建。需要強調的一點是,類執行個體的屬性設定順序應該是可以亂序的。屬性之間的設定應盡量正交,相互之間不會產生影響。
Sometimes, it’s useful to be able to reset a property. Then there are two approaches:
- pass a special value (such as QSize(), -1, or Qt::Alignment(0)) to mean “reset”
- have an explicit resetFoo() or unsetFoo() function
屬性的重設有兩種方法,使用通用API設定預設值,或者顯式調用重設介面。
Pointers vs. References
使用指標還是引用?
void getHsv(int *h, int *s, int *v) const
void getHsv(int &h, int &s, int &v) const
上面是兩種可能的API。參數應該用什麼形式?大多數書裡建議第二種,引用。因為引用更安全,更方便。但在設計API時,需要考慮調用方式。如:
color.getHsv(&h, &s, &v);
color.getHsv(h, s, v);
雖然都是調用,第一種明確告訴你參數可以被修改,但是第二種你完全不知道參數是否會被修改。
Virtual Functions
如果對於某類A,沒有任何類會繼承於他,他的變數不需要protected,他的函數也就不需要設定為虛函數。
There are many other reasons to avoid excessive use of virtual functions:
- you cannot add, move or remove virtual functions without breaking BC
- you cannot easily overload a virtual function
- compilers can almost never optimize or inline calls to virtual functions
- calling the function requires a v-table lookup, making it 2-3 times slower than a normal function
- virtual functions make the class hard to copy by value (possible, but very messy and discouraged)
Experience has taught us that a class with no virtual functions tends to have fewer bugs and generally causes less maintenance.
Input arguments: const pointers
Const functions that take input pointer arguments almost always take const pointer arguments.
類成員函數如果是const函數,通常該函數的輸入參數也是const類型的。
Return values: const values
non-const R-value不會檢查函數傳回值是否為const,因此可以修改non-const R-value函數的返回的引用對象。_const_ R-value返回的引用的const對象是不可被修改的。
另外需要強調的是,lvalue和rvalue。
Lvalue的意思是儲存地址屬性,rvalue指的是readable value,可以讀取的值屬性。而不是什麼左值右值。
Return values: pointers vs. const pointers
C++裡一般建議如果函數是常量函數,則返回參數也使用常量,避免返回一個非常量變數使得類對象有被修改的風險。但是返回參數使用常量可能會導致很難使用的API,可能會需要const_cast。所以,權衡風險,有時返回非常量變數會有更大的好處。
General Naming Rules
l 盡量避免使用縮寫。
l 設計類時,保證子類的命名空間乾淨。不要出現近似的詞語,如name,caption這樣的API名字人們很難分清。可以考慮使用更有意義的短語。
l 做好API的文檔注釋。如果一個函數或者變數沒法用合適的詞語精確描述,通常是這個條目不適合存在。如果你的確想描述他,就自己創造詞語描述他,並解釋清楚。
Naming Classes
Identify groups of classes instead of finding the perfect name for each individual class.
One guideline for naming enum types is to repeat at least one element of the enum type name in each of the enum values.
Naming Functions and Parameters
The number one rule of function naming is that it should be clear from the name whether the function has side-effects or not. Parameter names are an important source of information to the programmer.
Naming Boolean Getters, Setters, and Properties
- Adjectives are prefixed with is-. Examples:
- isChecked()
- isDown()
- isEmpty()
- isMovingEnabled()
- However, adjectives applying to a plural noun have no prefix:
- scrollBarsEnabled(), not areScrollBarsEnabled()
- Verbs have no prefix and don’t use the third person (-s):
- acceptDrops(), not acceptsDrops()
- allColumnsShowFocus()
- Nouns generally have no prefix:
- autoCompletion(), not isAutoCompletion()
- boundaryChecking()
- Sometimes, having no prefix is misleading, in which case we prefix with is-:
- isOpenGLAvailable(), not openGL()
- isDialog(), not dialog() (From a function called dialog(), we would normally expect that it returns a QDialog **.)
Avoiding Common Traps
The Convenience Trap
It is a common misconception that the less code you need to achieve something, the better the API.
最好不要讓函數參數過多,否則這樣的API設計出來很難懂。
The Boolean Parameter Trap
An obvious solution is to replace the bool parameters with enum types.
用enum結構替換,或者設計多個API代替原來的一個。