註解最重要的角色之一:定義抽象

抽象是「保留必要資訊、省略可忽略細節」的簡化視角;程式碼太低層、夾雜實作細節,不適合用來描述抽象

描述抽象的唯一方式 = 註解。

介面註解 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」給出精確意義
  • 重要資訊:這個方法必須最終被呼叫才能讓索引讀取持續推進