阿里開發手冊泰山版學習筆記四、編程規約-OOP規約

  1. 【強制】避免通過一個類的對象引用訪問此類的靜態變量或靜態方法,無謂增加編譯器解析成 本,直接用類名來訪問即可。

  2. 【強制】所有的覆寫方法,必須加@Override 註解。
    說明:getObject()與 get0bject()的問題。一個是字母的 O,一個是數字的 0,加@Override 可以準確判 斷是否覆蓋成功。另外,如果在抽象類中對方法簽名進行修改,其實現類會馬上編譯報錯。

  3. 【強制】相同參數類型,相同業務含義,纔可以使用 Java 的可變參數,避免使用 Object。
    說明:可變參數必須放置在參數列表的最後。(提倡同學們儘量不用可變參數編程)
    正例:public List listUsers(String type, Long… ids) {…}

  4. 【強制】外部正在調用或者二方庫依賴的接口,不允許修改方法簽名,避免對接口調用方產生 影響。接口過時必須加@Deprecated 註解,並清晰地說明採用的新接口或者新服務是什麼。

  5. 【強制】不能使用過時的類或方法。
    說明:java.net.URLDecoder 中的方法 decode(String encodeStr) 這個方法已經過時,應該使用雙參數 decode(String source, String encode)。接口提供方既然明確是過時接口,那麼有義務同時提供新的接口; 作爲調用方來說,有義務去考證過時方法的新實現是什麼。

  6. 【強制】Object 的 equals 方法容易拋空指針異常,應使用常量或確定有值的對象來調用 equals。
    正例:“test”.equals(object);
    反例:object.equals(“test”); 說明:推薦使用 java.util.Objects#equals(JDK7 引入的工具類)。

  7. 【強制】所有整型包裝類對象之間值的比較,全部使用 equals 方法比較。
    說明:對於 Integer var = ? 在-128 至 127 之間的賦值,Integer 對象是在 IntegerCache.cache 產生,會複用已有對象,這個區間內的 Integer 值可以直接使用==進行判斷,但是這個區間之外的所有數據,都會在堆上產生,並不會複用已有對象,這是一個大坑,推薦使用 equals 方法進行判斷。

  8. 【強制】任何貨幣金額,均以最小貨幣單位且整型類型來進行存儲。

  9. 【強制】浮點數之間的等值判斷,基本數據類型不能用==來比較,包裝數據類型不能用 equals來判斷。
    說明:浮點數採用“尾數+階碼”的編碼方式,類似於科學計數法的“有效數字+指數”的表示方式。二進制無法精確表示大部分的十進制小數,具體原理參考《碼出高效》。

反例:


float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
if (a == b) {
 // 預期進入此代碼快,執行其它業務邏輯
 // 但事實上 a==b 的結果爲 false
}
Float x = Float.valueOf(a);
Float y = Float.valueOf(b);
if (x.equals(y)) {
 // 預期進入此代碼快,執行其它業務邏輯
 // 但事實上 equals 的結果爲 false
}

正例:

(1) 指定一個誤差範圍,兩個浮點數的差值在此範圍之內,則認爲是相等的。
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
float diff = 1e-6f;
if (Math.abs(a - b) < diff) {
 System.out.println("true");
}
(2) 使用 BigDecimal 來定義值,再進行浮點數的運算操作。
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
if (x.equals(y)) {
 System.out.println("true");
}
  1. 【強制】定義數據對象 DO 類時,屬性類型要與數據庫字段類型相匹配。
    正例:數據庫字段的 bigint 必須與類屬性的 Long 類型相對應。
    反例:某個案例的數據庫表 id 字段定義類型 bigint unsigned,實際類對象屬性爲 Integer,隨着 id 越來越大,超過 Integer 的表示範圍而溢出成爲負數。

  2. 【強制】禁止使用構造方法 BigDecimal(double)的方式把 double 值轉化爲 BigDecimal 對象。
    說明:BigDecimal(double)存在精度損失風險,在精確計算或值比較的場景中可能會導致業務邏輯異常。如:BigDecimal g = new BigDecimal(0.1f); 實際的存儲值爲:0.10000000149
    正例:優先推薦入參爲 String 的構造方法,或使用 BigDecimal 的 valueOf 方法,此方法內部其實執行了
    Double 的 toString,而 Double 的 toString 按 double 的實際能表達的精度對尾數進行了截斷。

 BigDecimal recommend1 = new BigDecimal("0.1");
 BigDecimal recommend2 = BigDecimal.valueOf(0.1);
  1. 關於基本數據類型與包裝數據類型的使用標準如下:
    1)【強制】所有的 POJO 類屬性必須使用包裝數據類型。
    2)【強制】RPC 方法的返回值和參數必須使用包裝數據類型。
    3)【推薦】所有的局部變量使用基本數據類型。
    說明:POJO 類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何 NPE 問題,或者入庫檢查,都由使用者來保證。
    正例:數據庫的查詢結果可能是 null,因爲自動拆箱,用基本數據類型接收有 NPE 風險。
    反例:某業務的交易報表上顯示成交總額漲跌情況,即正負 x%,x 爲基本數據類型,調用的 RPC 服務,調用不成功時,返回的是默認值,頁面顯示爲 0%,這是不合理的,應該顯示成中劃線-。所以包裝數據類型的 null 值,能夠表示額外的信息,如:遠程調用失敗,異常退出。

  2. 【強制】定義 DO/DTO/VO 等 POJO 類時,不要設定任何屬性默認值。
    反例:POJO 類的 createTime 默認值爲 new Date(),但是這個屬性在數據提取時並沒有置入具體值,在更新其它字段時又附帶更新了此字段,導致創建時間被修改成當前時間。

  3. 【強制】序列化類新增屬性時,請不要修改 serialVersionUID 字段,避免反序列失敗;如果完全不兼容升級,避免反序列化混亂,那麼請修改 serialVersionUID 值。
    說明:注意 serialVersionUID 不一致會拋出序列化運行時異常。

  4. 【強制】構造方法裏面禁止加入任何業務邏輯,如果有初始化邏輯,請放在 init 方法中。

  5. 【強制】POJO 類必須寫 toString 方法。使用 IDE 中的工具:source> generate toString時,如果繼承了另一個 POJO 類,注意在前面加一下 super.toString。
    說明:在方法執行拋出異常時,可以直接調用 POJO 的 toString()方法打印其屬性值,便於排查問題。

  6. 【強制】禁止在 POJO 類中,同時存在對應屬性 xxx 的 isXxx()和 getXxx()方法。
    說明:框架在調用屬性 xxx 的提取方法時,並不能確定哪個方法一定是被優先調用到,神坑之一。

18.【推薦】使用索引訪問用 String 的 split 方法得到的數組時,需做最後一個分隔符後有無內容的檢查,否則會有拋 IndexOutOfBoundsException 的風險。
說明:

String str = "a,b,c,,";
String[] ary = str.split(",");
// 預期大於 3,結果是 3
System.out.println(ary.length)
  1. 【推薦】當一個類有多個構造方法,或者多個同名方法,這些方法應該按順序放置在一起,便於閱讀,此條規則優先於下一條。

  2. 【推薦】 類內方法定義的順序依次是:公有方法或保護方法 > 私有方法 > getter / setter
    方法。
    說明:公有方法是類的調用者和維護者最關心的方法,首屏展示最好;保護方法雖然只是子類關心,也可能是“模板設計模式”下的核心方法;而私有方法外部一般不需要特別關心,是一個黑盒實現;因爲承載的信息價值較低,所有 Service 和 DAO 的 getter/setter 方法放在類體最後。

  3. 【推薦】setter 方法中,參數名稱與類成員變量名稱一致,this.成員名 = 參數名。在getter/setter 方法中,不要增加業務邏輯,增加排查問題的難度。
    反例:

public Integer getData () {
 if (condition) {
 return this.data + 100;
 } else {
 return this.data - 100;
 }
}
  1. 【推薦】循環體內,字符串的連接方式,使用 StringBuilder 的 append 方法進行擴展。
    說明:下例中,反編譯出的字節碼文件顯示每次循環都會 new 出一個 StringBuilder 對象,然後進行 append操作,最後通過 toString 方法返回 String 對象,造成內存資源浪費。
    反例:
String str = "start";
for (int i = 0; i < 100; i++) {
 str = str + "hello";
}
  1. 【推薦】final 可以聲明類、成員變量、方法、以及本地變量,下列情況使用 final 關鍵字:
    1) 不允許被繼承的類,如:String 類。
    2) 不允許修改引用的域對象,如:POJO 類的域變量。
    3) 不允許被覆寫的方法,如:POJO 類的 setter 方法。
    4) 不允許運行過程中重新賦值的局部變量。
    5) 避免上下文重複使用一個變量,使用 final 可以強制重新定義一個變量,方便更好地進行重構。

  2. 【推薦】慎用 Object 的 clone 方法來拷貝對象。
    說明:對象 clone 方法默認是淺拷貝,若想實現深拷貝需覆寫 clone 方法實現域對象的深度遍歷式拷貝。

  3. 【推薦】類成員與方法訪問控制從嚴:
    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 成員方法或成員變量,刪除一下,不得手心冒點汗嗎?變量像自己的小孩,儘量在自己的視線內,變量作用域太大,無限制的到處跑,那麼你會擔心的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章