程序的庫設計
最近在 Stack Exchange 上面看到一個帖子,是問程序庫設計的指導原則的,“What guidelines should I follow while designing a library?”,有趣的是,很多人都在談論面向設計,各路 API 設計,還有程序語言設計,唯獨搜索“程序庫設計”,無論中文還是英文,Google 還是百度都找不到太多內容。但是我想,沒有程序員會否認庫設計的重要性吧,我想在這里結合這個帖子談談我的想法。
在這個帖子里面,votes 最高的回答,提到了這樣幾類 tips,我在下面簡要敘述一下,其中基礎的部分包括:
- Pin Map,明確你期望庫主要用來做什么,但不要把它定得太死,用戶要可以比較方便地做出改變。
- Working Library,一個工作的庫,如果它連這點都達不到,一定要注明。沒有人希望浪費時間在一個無法工作的程序庫上面。
- Basic Readme,清晰地描述庫是用來做什么的,測試的情況等等。
- Interfaces,接口必須清晰地定義,這可以幫助庫的使用者。
- Special Functions,特殊的功能,一定要注明,包括在 readme 文檔中注明,以及在注釋中注明。
- Busy Waits,如果有一些場景需要使用 busy wait(我不知道怎么翻譯),其過程中可能會出現異常,使用 interrupt 或者其它妥善的方法來處理。
- Comments,你做的任何的改變都要注釋清楚,明確描述接口和其每個參數,方法是做什么的,又返回什么;如果有某個中間方法被調用到,就要注明。
- Consistency,一致性,所有東西,包括注釋。相關的方法要放在一個簡單的代碼文件里面,小但是邏輯一致。
其中的高級部分又包括 Detailed Readme,Directory Structure,Licensing 和 Version Control。
這些都是需要注意的內容,并且大部分對于程序的庫設計來說是基礎要求,但是這些從重要性來說,并沒有說到點上。《C++沉思錄》里面有這樣一句 話:“庫設計就是語言設計,語言設計就是庫設計”,二者從先定義問題域到后解決問題的思路是類似的。我覺得比較重要的需要考慮的事情包括:
考慮庫的目標用戶。這聽起來扯得有點泛,但實際上這是確切的問題,這是開源庫還是你只是在小組內部使用的庫,或者是公司內部使用的?用戶的能力和需求是不一樣的,要求當然不同。
要解決的核心問題。這是上文中 Pin Map 的一部分,不要重復發明輪子,那么每一個新庫都有其存在的價值,這個問題既要通用又要具體,“通用”指的是庫總有一個普適性,解決的實際問題對于不同的用 戶來說是不一樣的;而“具體”是指庫解決的問題對于程序員來說是非常清晰和直接的。例如設計一個庫,根據某種規則把不同的數據類型(XML,BSON 或者某種基于行的文本等等)都轉換成 JSON。
統一的編程風格。很多庫都有自己精心設計的一套 DSL,比如鏈式調用等等幾種方式,當然,這也和使用的語言有關系。定義一種用起來舒服的編程風格對于程序庫的推廣是很有好處的。這也是一致性的一個體現。
內聚的調用入口。這和面向對象的“最少知識原則”有類似的地方,把那些不該暴露出去的庫內部實現信息隱藏起來,在很多情況下,程序庫不得不暴露和要求用戶了解一些知識的時候,比如:
MappingConfig config = new MappingConfig (); config.put (MappingConfigConstants.ENCODE, "UTF-8"); FileBuilder fileBuilder = new StandardFileBuilder (mapping); InputStream stream = fileBuilder.build () .getInputStream (); DataTransformer<XMLNode> transformer = new XMLDataTransformer (...); ... transformer.transform (stream);
這里引入了太多的概念,MappingConfig、FileBuilder、DataTransformer 等等,整個過程大致是構造了一個數據源,還有一個數據轉換器,然后這個數據轉換器接受這個數據源來轉換出最后結果的過程。那么:
這些象征著概念的接口和類最好以某種易于理解的形式組織起來,比如放在同一層比較淺的包里面,便于尋找;
也可以建立一個 facade 類,提供幾種常用的組合,避免這些繁瑣的對象構建和概念理解,例如:
XXFacade.buildCommonXMLTransformer ();
向后兼容。當然,這一點也可以歸納到前文提到的一致性里面去。我曾經拿 JDKHashTable 舉了一個例子,它的 containsValue 和 contains 方法其實是一樣的,造成這種情況的一個原因就是為了保持向后兼容。
依賴管理。依賴管理很多情況下是一個臟活累活,但是卻不得不考慮到。通常來說,任何一個庫考慮自己的依賴庫時都必須慎重,尤其是面對依賴的庫需要升級的時候。如果依賴的庫出了問題,自己設計的程序庫也可能因此連累。
完善的測試用例。通常來說,程序庫都配套有單元測試保證,無論是什么語言寫的。
健全的文檔組織。通常包括教程(tutorial)、開發者文檔(developer guide)和接口 API 文檔(API doc)。前者是幫助上手和建議使用的,中間的這個具備詳盡的特性介紹,后者則是傳統的 API 參考使用文檔。
<span id="shareA4" class="fl"> </span> </div>