Java API 設計清單

發表於2012-02-08

英文原文:theamiableapi,編譯:highkay@東西

在設計Java API的時候總是有很多不同的規範和考量。與任何複雜的事物一樣,這項工作往往就是在考驗我們思考的縝密程度。就像飛行員起飛前的檢查清單,這張清單將幫助軟體設計者在設計Java API的過程中回憶起那些明確的或者不明確的規範。本文也可以看作為“API設計指南”這篇文章的附錄。

我們還準備了一些前後比對的例子來展示這個列表如何幫助你理清設計需求,找出錯誤,識別糟糕的設計實踐以及如何尋找改進的時機。

這個清單使用瞭如下的語言規範:

要 – 表示必要的設計

建議 – 表示在幾個最好的設計中選擇一個

考慮 – 表示一個可能的設計上的改進

避免 – 表示一個設計上的缺陷

不要 – 表示一個設計上的錯誤

1. 包設計清單

1.1. 共通

▲1.1.1. 建議把API和實現放入不同的包

▲1.1.2. 建議把API放進上層包,而把實現放進下層包

▲1.1.3. strong>考慮把一組大型的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.5.1. 考慮為列舉型別指定一個0值(“NONE”或者“Unspecialized”等等)

▲2.5.2. 避免只有一個值的列舉

▲2.5.3. 不要使用列舉實現開放式的值集合

▲2.5.4. 不要為將來可能增加的值設計列舉

▲2.5.5. 不要為已經發布的版本增加新的列舉值

 

2.6. 異常

▲2.6.1. 確保自定義的異常可以被序列化

▲2.6.2. 考慮為每種型別定義一個不同的異常

▲2.6.3. 考慮為程式碼訪問提供更多的異常資訊

▲2.6.4. 避免深層的異常繼承

▲2.6.5. 不要從Exception和RuntimeException以外的類派生自定義異常

▲2.6.6. 不要直接從Throwable派生異常

▲2.6.7. 不要在異常資訊內包含敏感資訊

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的引數丟擲空指標異常

▲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.5.1. 使用@Override註解

▲3.5.2. 維持或弱化預置條件

▲3.5.3. 維持或者加強後置條件(不好翻譯,大概output+effect的意思)

▲3.5.4. 維持或者加強不可變性

▲3.5.5. 不要丟擲新增的執行時異常

▲3.5.6. 不要更改方法的型別(無狀態,訪問器或者擴充套件方法等)

3.6. 構造方法

▲3.6.1. 最小化構造方法中的工作

▲3.6.2. 為所有的屬性設定合理的預設值

▲3.6.3. 僅把構造方法的引數作為一種設定引數的快捷方法

▲3.6.4. 校驗構造方法的引數

▲3.6.5. 以引數相應的屬性為其命名

▲3.6.6. 當提供了多個構造方法時,遵循指南對其進行過載

▲3.6.7. 建議使用構造方法而不是靜態的工廠方法

▲3.6.8. 考慮使用無參的構造方法

▲3.6.9. 如果不是總需要新的例項,考慮使用靜態的工廠方法

▲3.6.10. 如果你需要在執行時決定一個合適的型別,考慮使用靜態的工廠方法

▲3.6.11. 如果你需要訪問外部的資源,考慮使用靜態的工廠方法

▲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.doId=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.8.1. 設計時使用最嚴密的預置條件

▲3.8.2. 設計時使用最弱的後置條件

▲3.8.3. 考慮傳遞引用物件的方法中把回撥介面作為第一個引數

▲3.8.4. 避免有返回值的回撥方法

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. 考慮為了描述方法的行為而包含單元測試

 

相關文章