雙人專案 徐銘宇 吳磊 程式碼規範
讀《阿里巴巴Java開發手冊華山版》,結合軟體工程導論課程所學內容整理
命名規約
【強制】 POJO 類中布林型別的變數,都不要加 is ,否則部分框架解析會引起序列化錯誤。
反例:定義為基本資料型別 boolean isSuccess;的屬性,它的方法也是 isSuccess() ,RPC框架在反向解析的時候,“以為”對應的屬性名稱是 success ,導致屬性獲取不到,進而丟擲異常。
對於isSuccess這個布林變數,IDE在自動生成getter,setter方法時,生成的方法名稱是isSuccess和setSuccess,而不是isIsSuccess和setIsSuccess,
public class DemoPOJO{ boolean active; boolean isSuccess; public boolean isActive() { return active; } public void setActive(boolean active) { this.active = active; } public boolean isSuccess() { return isSuccess; } public void setSuccess(boolean success) { isSuccess = success; } }
除了RPC框架反向解析會有問題,型別情況反向解析時也會有問題:
比如SpringMVC在接收前端頁面傳回一個"isSuccess"布林變數時,解析成為POJO物件時,找不到setIsSuccess方法,導致POJO的屬性不能正確獲取,而且比較坑的是,這種情況不容易發現異常,最終解析後的屬性值是拿到布林型別預設值false。
常量定義
【推薦】不要使用一個常量類維護所有常量,應該按常量功能進行歸類,分開維護。如:快取相關的常量放在類: CacheConsts 下 ; 系統配置相關的常量放在類: ConfigConsts 下。
說明:大而全的常量類,非得使用查詢功能才能定位到修改的常量,不利於理解和維護。
在寫程式碼的時候,從易用性和可維護性出發,不推薦一個類內容太多,大而全的類,改起來牽一髮動全身,一個類只負責一類功能,不要涵蓋太多方面。
OOP規約
【強制】所有的覆寫方法,必須加@ Override 註解。
反例: getObject() 與 get 0 bject() 的問題。一個是字母的 O ,一個是數字的 0,加@ Override可以準確判斷是否覆蓋成功。另外,如果在抽象類中對方法簽名進行修改,其實現類會馬上編譯報錯。
儘量用最安全的方式寫程式碼,儘量讓問題在編譯期暴露,而不是執行期暴露
【強制】 Object 的 equals 方法容易拋空指標異常,應使用常量或確定有值的物件來呼叫equals 。
正例:" test " .equals(object);
反例: object.equals( " test " );
說明:推薦使用 java . util . Objects # equals (JDK 7 引入的工具類 )
類似的還有使用 ""符號的時候,寫成:if(100 == sum)比起寫成:if(sum100),前者更好,因為這樣可以避免不小心寫成if(sum=100)的問題,前者會編譯報錯
【強制】所有的相同型別的包裝類物件之間值的比較,全部使用 equals 方法比較。
說明:對於 Integer var =?在-128 至 127 之間的賦值, Integer 物件是在IntegerCache.cache 產生,會複用已有物件,這個區間內的 Integer 值可以直接使用==進行判斷, 但是這個區間之外的所有資料, 都會在堆上產生, 並不會複用已有物件, 這是一個大坑,推薦使用 equals 方法進行判斷。
Integer integerA1 = 100;
Integer integerA2 = 100;
System.out.println(integerA1 == integerA2); //true
Integer integerB1 = 1000;
Integer integerB2 = 1000;
System.out.println(integerB1 == integerB2); //false
System.out.println(integerB1.equals(1000)); //true
【強制】關於基本資料型別與包裝資料型別的使用標準如下:
1 ) 所有的 POJO 類屬性必須使用包裝資料型別。
2 ) RPC 方法的返回值和引數必須使用包裝資料型別。
3 ) 所有的區域性變數【推薦】使用基本資料型別。
說明: POJO 類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何NPE 問題,或者入庫檢查,都由使用者來保證。
正例:資料庫的查詢結果可能是 null ,因為自動拆箱,用基本資料型別接收有 NPE 風險。
反例:比如顯示成交總額漲跌情況,即正負 x %, x 為基本資料型別,呼叫的 RPC 服務,呼叫不成功時,返回的是預設值,頁面顯示:0%,這是不合理的,應該顯示成中劃線-。所以包裝資料型別的 null 值,能夠表示額外的資訊,如:遠端呼叫失敗,異常退出。
NPE問題:空指標異常(Null Pointer Exception)
【強制】定義 DO / DTO / VO 等 POJO 類時,不要設定任何屬性預設值。
反例: POJO 類的 createTime 預設值為 new Date(); 但是這個屬性在資料提取時並沒有置入具體值,在更新其它欄位時又附帶更新了此欄位,導致建立時間被修改成當前時間。
【強制】 POJO 類必須寫 toString 方法。使用 IDE 的中工具: source > generate ,toString時,如果繼承了另一個 POJO 類,注意在前面加一下 super.toString 。
說明:在方法執行丟擲異常時,可以直接呼叫 POJO 的 toString() 方法列印其屬性值,便於排查問題。
【推薦】 類內方法定義順序依次是:公有方法或保護方法 > 私有方法 > getter / setter方法。
說明:公有方法是類的呼叫者和維護者最關心的方法,首屏展示最好;保護方法雖然只是子類關心,也可能是“模板設計模式”下的核心方法;而私有方法外部一般不需要特別關心,是一個黑盒實現;因為方法資訊價值較低,所有 Service 和 DAO 的 getter / setter 方法放在類體最後。
【推薦】類成員與方法訪問控制從嚴:
1 ) 如果不允許外部直接通過 new 來建立物件,那麼構造方法必須是 private 。
2 ) 工具類不允許有 public 或 default 構造方法。
3 ) 類非 static 成員變數並且與子類共享,必須是 protected 。
4 ) 類非 static 成員變數並且僅在本類使用,必須是 private 。
5 ) 類 static 成員變數如果僅在本類使用,必須是 private 。
6 ) 若是 static 成員變數,必須考慮是否為 final 。
7 ) 類成員方法只供類內部呼叫,必須是 private 。
8 ) 類成員方法只對繼承類公開,那麼限制為 protected 。
說明:任何類、方法、引數、變數,嚴控訪問範圍。過寬泛的訪問範圍,不利於模組解耦。思考:如果是一個 private 的方法,想刪除就刪除,可是一個 public 的 Service 方法,或者一個 public 的成員變數,刪除一下,不得手心冒點汗嗎?變數像自己的小孩,儘量在自己的視線內,變數作用域太大,如果無限制的到處跑,那麼你會擔心的。
1)將構造方法私有化,一般在單例模式下用得比較多,這時使用getInstance()方法來獲取一個例項物件。
2)工具類的話,應該暴露出來的方法是靜態方法,使用者靜態呼叫,不必例項化物件。
嚴格控制訪問範圍,也可以避免屬性值被不小心亂改。
集合處理
【強制】關於 hashCode 和 equals 的處理,遵循如下規則:
1) 只要重寫 equals ,就必須重寫 hashCode 。
2) 因為 Set 儲存的是不重複的物件,依據 hashCode 和 equals 進行判斷,所以 Set 儲存的物件必須重寫這兩個方法。
3) 如果自定義物件做為 Map 的鍵,那麼必須重寫 hashCode 和 equals 。
正例: String 重寫了 hashCode 和 equals 方法,所以我們可以非常愉快地使用 String 物件作為 key 來使用。
假如類User重寫了“equals”,沒有重寫“hashCode”方法,現在有userA和userB 2個物件,它們用equals比較時為true,2個物件存入一個Set集合中,Set呼叫User類預設的hashCode方法,結果在集合中就儲存了2個User物件而不是我們想象中的一個 User物件
關於快速重寫hashCode,有許多方法:
- Google的Guava專案裡有處理hashCode()和equals()的工具類
- com.google.common.base.ObjectsApache Commons也有類似的工具類EqualsBuilder和HashCodeBuilder
- Java 7 也提供了工具類java.util.Objects
- 常用IDE都提供hashCode()和equals()的程式碼生成。
【強制】 ArrayList 的 subList 結果不可強轉成 ArrayList , 否則會丟擲 ClassCastException異常: java . util . RandomAccessSubList cannot be cast to java . util . ArrayList ;
說明: subList 返回的是 ArrayList 的內部類 SubList ,並不是 ArrayList ,而是ArrayList 的一個檢視,對於 SubList 子列表的所有操作最終會反映到原列表上。
【強制】 在 subList 場景中,高度注意對原集合元素個數的修改,會導致子列表的遍歷、增加、刪除均產生 ConcurrentModificationException 異常。
【強制】使用工具類 Arrays . asList() 把陣列轉換成集合時,不能使用其修改集合相關的方法,它的 add / remove / clear 方法會丟擲UnsupportedOperationException 異常。
說明: asList 的返回物件是一個 Arrays 內部類,並沒有實現集合的修改方法。 Arrays . asList體現的是介面卡模式,只是轉換介面,後臺的資料仍是陣列。
String[] str = new String[] { "a", "b" };
List list = Arrays.asList(str);
第一種情況: list.add("c"); 執行時異常。
第二種情況: str[0]= "gujin"; 那麼 list.get(0) 也會隨之修改
類似問題,可以使用FindBugs外掛,自動掃描程式碼中的Bug,這類問題這個外掛是可以檢測出來的,類似的在物件中返回屬性域的一個引用時,對該引用的修改會影響原物件的域的。
【推薦】高度注意 Map 類集合 K / V 能不能儲存 null 值的情況,如下表格:
集合類 | Key | Value | Super | 說明 |
---|---|---|---|---|
Hashtable | 不允許為null | 不允許 null | Dictionary | 執行緒安全 |
ConcurrentHashMap | 不允許為 null | 不允許為 null | AbstractMap | 分段鎖技術 |
TreeMap | 不允許為null | 允許為 null | AbstractMap | 執行緒不安全 |
HashMap | 允許為 null | 允許為 null | AbstractMap | 執行緒不安全 |
反例: 由於 HashMap 的干擾,很多人認為 ConcurrentHashMap 是可以置入 null 值,注意儲存null 值時會丟擲 NPE 異常。
【參考】利用 Set 元素唯一的特性,可以快速對一個集合進行去重操作,避免使用 List的contains 方法進行遍歷、對比、去重操作。