在設計Java API的時候總是有很多不同的規範和考量。與任何複雜的事物一樣,這項工作往往就是在考驗我們思考的縝密程度。就像飛行員起飛前的檢查清單,這張清單將協助軟體設計者在設計Java API的過程中回憶起那些明確的或者不明確的規範。本文也可以看作為“API設計指南”這篇文章的附錄。
我們還準備了一些前後比對的例子來展示這個列表如何協助你理清設計需求,找出錯誤,識別糟糕的設計實踐以及如何尋找改進的時機。
這個清單使用了如下的語言規範:
要 - 表示必要的設計
建議 - 表示在幾個最好的設計中選擇一個
考慮 - 表示一個可能的設計上的改進
避免 - 表示一個設計上的缺陷
不要 - 表示一個設計上的錯誤
1. 包設計清單
1.1. 共通
1.1.1. 建議把API和實現放入不同的包
1.1.2. 建議把API放進上層包,而把實現放進下層包
1.1.3. 考慮把一組大型的API分拆進不同的包
1.1.4. 考慮把API和實現打包進不同的jar包
1.1.5. 避免API的實作類別之間的內部依賴
1.1.6. 避免把API分拆了太細
1.1.7. 避免把公用實作類別放到API的包中
1.1.8. 不要在調用者和實作類別之間建立依賴
1.1.9. 不要把沒有關係的API放進同一個包
1.1.10. 不要把API和SPI(service provider interface)放進一個包(註:兩者不同可以查看這個頁面http://stackoverflow.com/questions/2954372/difference-between-spi-and-api )
1.1.11. 不要移動或者重新命名一個已經發布的公用API
1.2. 命名
1.2.1. (一級)包名以公司(或者組織)的根命名空間來命名
1.2.2. 使用一個穩定的產品名稱或者一個產品系列的名稱作為包的二級名稱
1.2.3. 使用API名稱作為包名的(三級名稱)結尾
1.2.4. 考慮把僅包含實現的包的名稱中包含"internal"這個詞(註:似乎“impl”更常見一些)
1.2.5. 避免使用組合起來的名稱
1.2.6. 避免包名和包內的類名使用同樣的名稱
1.2.7. 避免在包名稱中使用“api”這個詞
1.2.8. 不要使用營銷,計劃,組織單元(部門)和地理名稱
1.2.9. 不要在包名中使用大寫字母
1.3. 文檔
1.3.1. 為每一個包提供一個package.html
1.3.2. 遵循標準的javadoc的規範
1.3.3. 在API的開始處用一句短小的話來概括(描述)
1.3.4. 提供足夠多的細節來協助判斷是否需要使用和如何使用該API
1.3.5. 指出該API的入口(主要的類或者方法)
1.3.6. 包含覆蓋主要的,準系統示範的範例代碼
1.3.7. 包含一個指向開發人員指南的超連結
1.3.8. 包含一個指向手冊的超連結
1.3.9. 指出相關的API集合
1.3.10. 包含API的版本號碼
1.3.11. 用 @deprecated 標記出不再使用的API版本
1.3.12. 考慮添加一個著作權聲明
1.3.13. 避免過長的包概述
1.3.14. 不要在發布的javadoc中包含實現相關的包
2. 類型設計清單(這裡的“類型”個人理解為一組Api)
2.1. 共通
2.1.1. 確保每種(設計的)類型都有單一明確的目的
2.1.2. 確保每種類型代表了(業務)領域的概念,而不是為了(技術上)的抽象
2.1.3. 限制類型的總數量
2.1.4. 限制類型的大小
2.1.5. 設計相關的類型時保持和原有的類型的一致性
2.1.6. 建議為多種public的類型提供多種(private)的實現
2.1.7. 建議介面的實作類別和繼承關係的類應該在行為上保持一致性
2.1.8. 建議用抽象類別而不是介面解耦Api的實現
2.1.9. 建議使用枚舉而不是常量
2.1.10. 考慮使用泛型
2.1.11. 考慮在泛型參數上增加約束
2.1.12. 考慮使用介面來實現多繼承的效果
2.1.13. 避免為使用者的擴充(需求)進行設計
2.1.14. 避免深度的繼承層次
2.1.15. 不要使用public內嵌的類型
2.1.16. 不要申明public和protected的變數
2.1.17. 不要把實現的繼承關係暴露給使用者
2.2. 命名
2.2.1. 使用名詞或者名詞片語
2.2.2. 使用PascalCasing(駝峰命名法的別稱詳見http://en.wikipedia.org/wiki/CamelCase )
2.2.3. 縮寫僅第一個首字母大寫
2.2.4. 為類型的實際作用使用精確的名稱命名
2.2.5. 為最常用的類型準備最短最容易記憶的名稱
2.2.6. 所有異常都以“Exception”結尾
2.2.7. 使用名詞的單數(比如用Color而不用Colors)來命名枚舉類型
2.2.8. 考慮較長的名稱
2.2.9. 考慮衍生類別用基類的名稱結尾
2.2.10. 考慮為抽象類別名稱使用“Abstract”開頭
2.2.11. 避免使用縮減語
2.2.12. 避免太通用的名詞
2.2.13. 避免同義字
2.2.14. 避免在相關的Api中使用類型的名稱
2.2.15. 不要使用僅大小寫不同的名稱
2.2.16. 不要使用首碼
2.2.17. 不要以“I”作為介面的名稱開頭
2.2.18. 不要(重複)使用Java核心包中的名稱
2.3. 類
2.3.1. 最小化實現使用的依賴
2.3.2. 先列出public方法
2.3.3. 申明實現方法為private(這裡是筆誤嗎?)
2.3.4. 為一個public抽象類別定義至少一個public的shi
2.3.5. 為基本的使用方式提供足夠的預設實現
2.3.6. 設計基本上不變的類
2.3.7. 把無狀態,訪問器,擴充(mutator個人理解為多種參數形式的方法)方法集合到一起
2.3.8. 把擴充方法的數量控制到最少
2.3.9. 考慮設計一個預設的無參的構造方法
2.3.10. 考慮重寫equal,hashCode方法
2.3.11. 考慮實現Comparable介面
2.3.12. 考慮實現Serializable介面
2.3.13. 考慮使類可以容易的擴充
2.3.14. 考慮申明類為final
2.3.15. 考慮為類的執行個體化提供一個public的構造方法
2.3.16. 考慮使用自訂的類型來增強類的不可變性
2.3.17. 考慮設計不可變的類
2.3.18. 避免靜態類
2.3.19. 避免使用Cloneable
2.3.20. 不要向靜態類中添加執行個體duixi
2.3.21. 不要為使用者不應擴充的public抽象類別提供public的構造方法
2.3.22. 不要濫用初始化
2.4. 介面
2.4.1. 為每一個public介面提供至少一個實作類別
2.4.2. 為每一個public介面設計至少一個消費方法
2.4.3. 不要對一個已經發布的public介面添加新的方法
2.4.4. 不要使用標記介面(標記介面詳見http://en.wikipedia.org/wiki/Marker_interface_pattern )
2.4.5. 不要把public介面設計成常量的容器(這個實在很常見啊……)
2.5. 枚舉
2.6. 異常
2.7. 文檔
2.7.1. 為每種類型(的Api)配上概述
2.7.2. 遵循標準Javadoc的約定
2.7.3. 每種類型開頭以一句短小的話概述
2.7.4. 為是否使用以及如何使用該類型提供足夠的細節來協助做決定
2.7.5. 解釋如何執行個體化一個類型
2.7.6. 為一個類型的主要的使用情景提供範例代碼
2.7.7. 包含指向到開發指南的連結
2.7.8. 包含指向手冊的連結
2.7.9. 顯示相關的類型
2.7.10. 用@deprecated標籤申明過時的類型
2.7.11. 文檔類具有不可變性
2.7.12. 避免冗長的類概述
2.7.13. 不要為私人方法產生Javadoc
3. 方法設計清單
3.1. 共通
3.1.1. 確保每個方法實現一個目的
3.1.2. 確保相關的方法都是一個粒度層級的
3.1.3. 確保沒有混合調用方法的公用代碼
3.1.4. 使所有方法的調用具有原子性(原子性:http://jiangyongyuan.iteye.com/blog/364010)
3.1.5. 設計protected方法時要像public方法一樣謹慎
3.1.6. 限制擴充方法的數量
3.1.7. 設計擴充方法需要具有較強的穩定性
3.1.8. 建議為一系列重載的方法設計一個泛型的方法
3.1.9. 考慮使用泛型方法
3.1.10. 考慮設計方法對,即兩個方法的作用是相反的
3.1.11. 避免“helper”方法
3.1.12. 避免長時間執行的方法
3.1.13. 避免調用者在普通使用中需要手動寫迴圈
3.1.14. 避免可選的參數影響方法的行為
3.1.15. 避免不可重複調用的方法
3.1.16. 不要刪除一個已經發布的方法
3.1.17. 不要在沒有提供替換方法前把一個已經發布的方法標記為過時
3.1.18. 不要修改一個已經發布的方法的簽名
3.1.19. 不要修改一個已經發布的方法的可觀測行為(也許指的是輸出之類)
3.1.20. 不要增加一個已經發布方法的調用條件
3.1.21. 不要減少一個已經發布方法的調用結果
3.1.22. 不要為已經發布的public介面新增方法
3.1.23. 不要為已經發布的Api新增重載
3.2. 命名
3.2.1. 用給力的,有表達力的動詞作為名稱起始
3.2.2. 使用駝峰命名法(好奇怪,前面寫的是PascalNaming)
3.2.3. 為JavaBean的私人屬性預留“get”“set”“is”等存取方法
3.2.4. 使用對調用者熟悉的詞語
3.2.5. 盡量使用英語口語
3.2.6. 避免使用縮減語
3.2.7. 避免使用一般的動詞
3.2.8. 避免同義字
3.2.9. 不要使用“黑話”
3.2.10. 不要依靠參數的名稱和類型判斷方法的意義
3.3. 參數
3.3.1. 為參數選擇最合適的類型
3.3.2. 在相關方法的調用中對參數為null值的處理保持一致性
3.3.3. 在相關方法中參數的名稱,類型和順序需要保持一致
3.3.4. 在參數列表中把輸出的參數放到輸入參數之後
3.3.5. 為重載的方法省略常用的預設參數以提供一個較短的參數列表
3.3.6. 在無關的類型中為相同語義的操作提供重載方法
3.3.7. 建議使用介面而不是具體類作為參數
3.3.8. 建議使用集合而不是數組作為參數和傳回值
3.3.9. 建議使用一般集合而不是原始(無類型)集合
3.3.10. 建議使用枚舉而不是Boolean或者Integer作為參數
3.3.11. 建議把單個的參數放到集合或者數組參數之前
3.3.12. 建議把自訂類型的參數放大Java標準型別參數之前
3.3.13. 建議把物件類型的參數方法實值型別的參數之前
3.3.14. 建議使用介面而不是具體類作為傳回值
3.3.15. 建議把空的集合而不是null作為傳回值
3.3.16. 建議把傳回值設計成可以作為其他方法的合法輸入參數
3.3.17. 考慮為不可變參數設計一個副本
3.3.18. 考慮在內部儲存弱引用的對象
3.3.19. 避免參數數量變更
3.3.20. 避免參數長度太長(超過3個)
3.3.21. 避免連續的同類型的參數
3.3.22. 避免用作輸出或者輸入輸出的參數
3.3.23. 避免方法重載
3.3.24. 避免參數類型暴露實現細節
3.3.25. 避免boolean參數
3.3.26. 避免返回null
3.3.27. 除了Java核心Api,避免把類型作為不相關的Api的傳回值
3.3.28. 避免把可變的內部對象作為傳回值來引用
3.3.29. 不要把預先設定的常量作為整型值參數使用
3.3.30. 不要為將來的(擴充設計)考慮預留參數
3.3.31. 不要在重載方法中改變參數的名稱的順序
3.4. 異常處理
3.4.1. 只有在異常情況下才拋出異常
3.4.2. 只需要為可恢複的錯誤拋出已確認的異常
3.4.3. 為了通知Api使用錯誤而拋出運行時異常
3.4.4. 在適當的抽象層次拋出異常
3.4.5. 進行運行時預置條件的檢查
3.4.6. 為一個被不能為null的參數拋出null 指標異常
3.4.7. 為一個除為null以外異常值的參數排除非法參數異常
3.4.8. 為一個錯誤上下文環境中的方法調用拋出非法狀態異常
3.4.9. 在錯誤資訊中顯示出參數的預置條件
3.4.10. 確保失敗的方法調用不會產生單向的後果
3.4.11. 為回調方法中的禁止使用的Api提供運行時檢查
3.4.12. 建議優先使用Java標準異常
3.4.13. 建議提供拋出異常的條件的查詢方法
3.5. 重寫
3.6. 構造方法
3.6.1. 最小化構造方法中的工作
3.6.2. 為所有的屬性設定合理的預設值
3.6.3. 僅把構造方法的參數作為一種設定參數的快捷方法
3.6.4. 校正構造方法的參數
3.6.5. 以參數相應的屬性為其命名
3.6.6. 當提供了多個構造方法時,遵循指南對其進行重載
3.6.7. 建議使用構造方法而不是靜態Factory 方法
3.6.8. 考慮使用無參的構造方法
3.6.9. 如果不是總需要新的執行個體,考慮使用靜態Factory 方法
3.6.10. 如果你需要在運行時決定一個合適的類型,考慮使用靜態Factory 方法
3.6.11. 如果你需要訪問外部的資源,考慮使用靜態Factory 方法
3.6.12. 當面臨非常多的參數的時候,考慮使用產生器(builder)
3.6.13. 當需要迴避直接執行個體化類的時候使用考慮private的建構函式
3.6.14. 避免建立非必需的對象
3.6.15. 避免finalizer
3.6.16. 不要從無參的構造方法中拋出異常
3.6.17. 不要向一個已經發布的類中添加顯示的構造方法
3.7. Setters和getters
3.7.1. 以get開頭命名一個傳回值不為boolean的訪問屬性的方法
3.7.2. 以is,can開頭命名一個傳回值為boolean的訪問屬性的方法
3.7.3. 以set開頭命名一個更新本地變數的方法
3.7.4. 校正setter方法的參數
3.7.5. 最小化getter和setter方法的工作
3.7.6. 考慮從一個getter方法中返回不可變的集合
3.7.7. 考慮實現一個private介面的集合替代public的集合屬性
3.7.8. 考慮唯讀屬性
3.7.9. 設定可變類型的屬性時考慮Defensive Copy(Defensive Copy詳見:http://www.javapractices.com/topic/TopicAction.do?Id=15 )
3.7.10. 當返回可變類型的屬性時考慮Defensive Copy
3.7.11. getter方法避免返回數組
3.7.12. 避免根據方法內資訊無法完成的校正
3.7.13. 不要從getter方法中拋出異常
3.7.14. 不要設計只能set的屬性方法 (僅有public的setter而沒有public的getter)
3.7.15. 不要相依性屬性設定的順序
3.8. 回調
3.9. 文檔
3.9.1. 為每個方法提供Javadoc注釋
3.9.2. 遵循標準的Javadoc約定
3.9.3. 每個方法以一句短小的話作為概述
3.9.4. 申明相關的方法
3.9.5. 用@deprecated標籤申明過時的類型
3.9.6. 顯示所有過時方法的替換方法
3.9.7. 避免冗長的zhus
3.9.8. 包含常用的使用模式
3.9.9. (如果允許的話)包含null值的確切含義
3.9.10. 包含方法的類型 (無狀態,訪問器或者擴充)
3.9.11. 包含方法的預置條件
3.9.12. 包含演算法實現的效能特徵
3.9.13. 包含遠程方法調用
3.9.14. 包含訪問外部資源的方法
3.9.15. 包含哪些API可以在回調中使用
3.9.16. 考慮為了描述方法的行為而包含單元測試
英文原文:http://theamiableapi.com/2012/01/16/java-api-design-checklist/
原文連結:http://dongxi.net/b14gn