註解最重要的角色之一:定義抽象。
抽象是「保留必要資訊、省略可忽略細節」的簡化視角;程式碼太低層、夾雜實作細節,不適合用來描述抽象。
描述抽象的唯一方式 = 註解。
介面註解 vs. 實作註解#
| 類型 | 內容 | 受眾 |
|---|---|---|
| 介面註解 | 使用者使用本類別 / 方法所需知道的資訊 | 使用者 |
| 實作註解 | 內部如何運作以實現抽象 | 維護者 |
兩者必須分開,使用者不應被迫看到實作細節。
而且,兩者內容應該不同——若介面註解必須描述實作,那這個類別 / 方法太淺。
因此「寫註解的過程」本身就提供了「設計品質」的線索(第 15 章再展開)。
類別的介面註解範例#
/**
* This class implements a simple server-side interface to the HTTP
* protocol: by using this class, an application can receive HTTP
* requests, process them, and return responses. Each instance of
* this class corresponds to a particular socket used to receive
* requests. The current implementation is single-threaded and
* processes one request at a time.
*/
public class Http { ... }- 描述類別的整體能力
- 不涉及實作細節,也不涉及具體方法
- 描述每個 instance 代表什麼
- 描述類別的限制(單執行緒)—— 這對潛在使用者很重要
方法介面註解必含#
- 行為描述:一兩句話描述方法在呼叫端視角下的行為(高層抽象)
- 每個參數與回傳值:精確;包括參數值的限制與彼此之間的依賴
- 副作用(side effects):影響系統未來行為但不屬回傳結果的後果
- 例如:把值加進內部資料結構、寫入檔案系統等
- 可能拋出的例外
- 前置條件(preconditions):呼叫前必須滿足的條件
- 例如:先呼叫某方法、二分搜尋的列表必須已排序
應儘量減少前置條件,但剩下的必須記錄清楚。
範例:方法介面註解#
/**
* Copy a range of bytes from a buffer to an external location.
*
* \param offset
* Index within the buffer of the first byte to copy.
* \param length
* Number of bytes to copy.
* \param dest
* Where to copy the bytes: must have room for at least
* length bytes.
*
* \return
* The return value is the actual number of bytes copied,
* which may be less than length if the requested range of
* bytes extends past the end of the buffer. 0 is returned
* if there is no overlap between the requested range and
* the actual buffer.
*/
uint32_t Buffer::copy(uint32_t offset, uint32_t length, void* dest)特點:
- 提供開發者呼叫所需的全部資訊
- 描述特殊情況的處理(依第 10 章原則:將錯誤從定義中消除)
- 開發者不需要讀方法內部就能呼叫
- 不暴露實作細節
較長的範例:IndexLookup 類別#
IndexLookup 是分散式儲存系統的客戶端類別:
- 每個 table 可有多個 index,依某 field 加速查詢
- 給定 table、index、key 範圍 → 重複呼叫
getNext取得區間內每個物件
哪些資訊應該放進類別介面註解?#
考慮以下幾項:
| 項目 | 是否應出現在介面註解 |
|---|---|
1. IndexLookup 與索引 / 物件伺服器之間的訊息格式 | 不(實作細節,應隱藏) |
| 2. 範圍比較使用的比較函式(整數、浮點、字串?) | 是(使用者必須知道) |
| 3. 伺服器端用來儲存索引的資料結構 | 不(連 IndexLookup 實作都不需要知道) |
| 4. 是否同時對多個伺服器發送請求 | 可能(若使用了影響觀察行為的特殊技術,需提及) |
| 5. 處理伺服器當機的機制 | 不(系統自動恢復,使用者看不到) |
反例:原始介面註解(含太多實作)#
/*
* This class implements the client side framework for index range
* lookups. It manages a single LookupIndexKeys RPC and multiple
* IndexedRead RPCs. Client side just includes "IndexLookup.h" in
* its header to use IndexLookup class. Several parameters can be
* set in the config below:
* - The number of concurrent indexedRead RPCs
* - The max number of PKHashes a indexedRead RPC can hold at a time
* - The size of the active PKHashes
*
* To use IndexLookup, the client creates an object of this class by
* providing all necessary information. After construction of
* IndexLookup, client can call getNext() function to move to next
* available object. ...
*/問題:
- 第一段大半談實作(具體 RPC 名、private 設定參數)
- 「include
IndexLookup.h」是 C++ 常識,不需要說 - 「by providing all necessary information」是廢話
改善後#
/*
* This class is used by client applications to make range queries
* using indexes. Each instance represents a single range query.
*
* To start a range query, a client creates an instance of this
* class. The client can then call getNext() to retrieve the objects
* in the desired range. For each object returned by getNext(), the
* caller can invoke getKey(), getKeyLength(), getValue(), and
* getValueLength() to get information about that object.
*/最後一段嚴格說不必要(與個別方法的註解重複),但對深類別的非顯而易見用法,整體使用範例對讀者有幫助。
這個註解沒有提到
getNext的 NULL 回傳——細節留給方法的介面註解。也沒有提到伺服器當機處理——對使用者不可見。
紅旗:實作文件汙染介面(Red Flag: Implementation Documentation Contaminates Interface)#
介面文件中描述了「使用本實體不需要知道」的實作細節 → 紅旗。
反例:方法層級的「實作汙染介面」#
/**
* Check if the next object is RESULT_READY. This function is
* implemented in a DCFT module, each execution of isReady() tries
* to make small progress, and getNext() invokes isReady() in a
* while loop, until isReady() returns true.
*
* isReady() is implemented in a rule-based approach. We check
* different rules by following a particular order, and perform
* certain actions if some rule is satisfied.
*
* \return
* True means the next Object is available. Otherwise, return
* false.
*/
bool IndexLookup::isReady() { ... }問題:
- DCFT、rule-based、
getNext的迴圈方式 → 全是實作 - 「RESULT_READY」是隱晦縮寫
- 重要資訊缺漏
改善#
/*
* Indicates whether an indexed read has made enough progress for
* getNext to return immediately without blocking. In addition, this
* method does most of the real work for indexed reads, so it must
* be invoked (either directly, or indirectly by calling getNext) in
* order for the indexed read to make progress.
*
* \return
* True means that the next invocation of getNext will not block
* (at least one object is available to return, or the end of the
* lookup has been reached); false means getNext may block.
*/- 對「ready」給出精確意義
- 重要資訊:這個方法必須最終被呼叫才能讓索引讀取持續推進