欲開發乙個「容易被正確使用,不容易被誤用」的介面,首先必須考慮客戶可能做出什麼樣的錯誤。假設你為乙個用來表現日期的class設計建構函式:
class date ;
乍見之下這個介面通情達理(至少在美國如此),但它的客戶很容易犯下至少兩個錯誤。
第一,他們也許會以錯誤的次序傳遞引數:
date(30, 3, 1995); // 應該是「3, 30」
第二,他們可能傳遞乙個無效的月份或者天數:
date(2, 30, 1995); // 應該是「3, 30」
struct day
int val;
};struct month
int val;
};struct year
int val;
};class date ;
date d(30, 3, 1995); // 錯誤!不正確的型別
date d(day(30), month(3), year(1995)); // 錯誤!不正確的型別
date d(month(3), day(30), year(1995)); // ok,型別正確
令day,month和year成為成熟且經充分鍛鍊的classes並封裝其內資料,比簡單使用上述的structs好(見條款22)。但即使structs也已經足夠示範:明智而審慎地匯入新型別對預防「介面被誤用」有神奇療效。
一旦正確的型別定位,限制其值有時候是通情達理的。例如一年只有12個有效月份,所以month應該反映這一事實。辦法之一是利用enum表現月份,但enums不具備我們希望擁有的型別安全性,例如enums可被拿來當乙個ints使用(見條款2)。
比較安全的解法是預先定義所有有效的months:
class month // 函式,返回有效月份。
static month feb() // 稍後解釋為什麼。
... // 這些是函式而非物件。
static month dec()
...private:
explict month(int m); // 阻止生成新的月份。
... // 這是月份專屬資料。
};date d(month::mar(), day(30), year(1995));
如果「以函式替換物件,表現某個特定月份」讓你覺得詭異,或許是因為你忘記了non-local static物件的初始化次序有可能出問題。建議閱讀條款4恢復記憶。
上述處理方式,目前看來太過繁瑣,通常專案開發中,我們可以使用斷言來判定介面引數是否合法。主要是要理解上述內容的思想。可以使用新型別,確保提供給使用者的介面方便使用且不易出錯。
預防客戶錯誤的另乙個辦法是,限制型別內什麼事可做,什麼事不能做。常見的限制是加上const。例如條款3曾經說明為什麼「以const修飾operator*的返回型別」可阻止客戶因「使用者自定義型別」而犯錯:
if (a * b = c) ... // 原意其實是要做一次比較動作!
下面另乙個一般性準則「讓types容易被正確使用,不容易被誤用」的表現形式:「除非有好理由,否則應該盡量令你的types的行為與內建types一致」。客戶已經知道像int這樣的type有些什麼行為,所以你應該努力讓你的types在合樣合理的前提下也有相同表現。例如,如果a和b都是ints,那麼對a*b賦值並不合法。
任何介面如果要求客戶必須記得做某些事情,就是有著「不正確使用」的傾向,因為客戶可能會忘記做那件事。例如條款13匯入了乙個factory函式,它返回乙個指標指向investment繼承體系內的乙個動態分配物件:
investment* createinvestment(); // 來自條款13;為求簡化暫略引數。
為避免資源洩漏,createinvestment返回的指標最終必須被刪除,但那至少開啟了兩個客戶錯誤機會:沒有刪除指標,或刪除同乙個指標超過一次。
條款13表明客戶如何將createinvestment的返回值儲存於乙個智慧型指標如auto_ptr或tr1::shared_ptr內,因而將delete責任推個智慧型指標。但萬一客戶忘記使用智慧型指標怎麼辦?許多時候,較佳介面的設計原則是先發制人,就令factory函式返回乙個智慧型指標:
std::tr1::shared_ptrcreateinvestment();
這便實質上強迫客戶將返回值儲存於乙個tr1::shared_ptr內,幾乎消弭了忘記刪除底部investment物件(當它不再被使用時)的可能性。
實際上,返回tr1::shared_ptr讓介面設計者得以阻止一大群客戶犯下資源洩漏的錯誤,因為就如條款14所言,tr1::shared_ptr允許當智慧型指標被建立起來時指定乙個資源釋放函式(所謂刪除器,「deleter」)繫結於智慧型指標身上(auto_ptr就沒有這種能耐)。
本條款並非特別針對tr1::shared_ptr,而是為了「讓介面容易被正確使用,不容易被誤用」而設。但由於tr1::shared_ptr如此容易消除某些客戶錯誤,值得我們核計其使用成本。最常見的tr1::shared_ptr實現來自boost(見條款55)。boost的shared_ptr是原始指標(raw pointer)的兩倍大,以動態分配記憶體作為薄記用途和「刪除器之專屬資料」,以virtual形式呼叫刪除器,並在多執行緒程式修改引用次數時蒙受執行緒同步化的額外開銷。總之,它比原始指標大且慢,而且使用輔助動態記憶體。在許多應用程式中這些額外的執行成本並不顯著,然而其「降低客戶錯誤」的成效卻是每個人都看得到。
18 讓介面容易被正確使用,不易被誤用
1 所謂軟體設計,就是 讓軟體做出你期望它做的事情 的步驟。首先是構想,考慮對外暴露的介面,然後實現。2 客戶沒有正確使用自己提供的介面,自己也要負擔一部分責任,思考自己的介面是不是簡單明瞭,容易理解。3 對於多個形參的介面,最好表明每個形參的型別和有效範圍。4 盡量限制哪些事能做,哪些事不能做,盡...
條款18 讓介面容易被正確使用,不易被誤用
條款18 讓介面容易被正確使用,不易被誤用 make inte ces easy to use correctly and hard to use incorrectly.內容 假設現在的你需要提供一些介面給你的客戶去使用,而現在的你沒有任何這個方面的經驗,那麼你就要考慮 下面這些情況的發生 1 你...
如何讓技術想法更容易被理解
1 技術問題描述不清,解決效率低下 同學a在交付現場遇到技術問題,在專案群裡面求助同學b的解決過程。同學a 大佬,雲架構改了之後,資料庫規劃不了。同學a 解決方案導不出來。其他同學cdef 其他問題討論,衝亂了上下文。同學a 資料庫不能規劃,報風險。同學b 先把問題描述清楚。同學a可能的心理狀態 這...