【開發手冊】-這些規約你遵守了麼?

前言

《Java開發手冊》是阿里巴巴集團技術團隊的集體智慧結晶和經驗總結,經歷了多次大規模一線實戰的檢驗及不斷完善,公開到業界後,衆多社區開發者踊躍參與,共同打磨完善,系統化地整理成冊。現代軟件行業的高速發展對開發者的綜合素質要求越來越高,因爲不僅是編程知識點,其它維度的知識點也會影響到軟件的最終交付質量。比如:數據庫的表結構和索引設計缺陷可能帶來軟件上的架構缺陷或性能風險;工程結構混亂導致後續維護艱難;沒有鑑權的漏洞代碼易被黑客攻擊等等。所以本手冊以Java開發者爲中心視角,劃分爲編程規約、異常日誌、單元測試、安全規約、MySQL數據庫、工程結構、設計規約七個維度,再根據內容特徵,細分成若干二級子目錄。另外,依據約束力強弱及故障敏感性,規約依次分爲強制、推薦、參考三大類。在延伸信息中,說明”對規約做了適當擴展和解釋;“正例”提倡什麼樣的編碼和實現方式;“反例”說明需要提防的雷區,以及真實的錯誤案例。

       手冊的願景是碼出高效,碼出質量。現代軟件架構的複雜性需要協同開發完成,如何高效地協同呢?無規矩不成方圓,無規範難以協同,比如,制訂交通法規表面上是要限制行車權,實際上是保障公衆的人身安全,試想如果沒有限速,沒有紅綠燈,誰還敢上路行駛?對軟件來說,適當的規範和標準絕不是消滅代碼內容的創造性、優雅性,而是限制過度個性化,以一種普遍認可的統一方式一起做事,提升協作效率,降低溝通成本。代碼的字裏行間流淌的是軟件系統的血液,質量的提升是儘可能少踩坑,杜絕踩重複的坑,切實提升系統穩定性,碼出質量。

這些規範你遵守了麼?

一下列舉大家可能編程中不會注意到的點,序號爲開發手冊序號

一、編程規約

(一)命名規範

2.【強制】代碼中的命名嚴禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。

  • 反例:DaZhePromotion[打折]/getPingfenByName()[評分]

8.【強制】POJO類中布爾類型變量都不要加is前綴,否則部分框架解析會引起序列化錯誤。 

  • 反例:Boolean  isDeleted;

12.【推薦】爲了達到代碼自解釋的目標,任何自定義編程元素在命名時,使用盡量完整的單詞組合來表達其意。

  • 反例:inta的隨意命名方式。

13.【推薦】在常量與變量的命名時,表示類型的名詞放在詞尾,以提升辨識度。 

  • 正例:startTime   /workQueue        /nameList      /TERMINATED_THREAD_COUNT
  • 反例:startedAt    /QueueOfWork   /listName      /COUNT_TERMINATED_THREAD

15.【推薦】接口類中的方法和屬性不要加任何修飾符號(public也不要加),保持代碼的簡潔性,並加上有效的Javadoc註釋儘量不要在接口裏定義變量,如果一定要定義變量,肯定是與接口方法相關,並且是整個應用的基礎常量。

  • 正例:接口方法簽名voidcommit();                                                                                                                                               接口基礎常量StringCOMPANY="alibaba";
  • 反例:接口方法定義publicabstractvoidf();

(二) 常量定義

1. 【強制】不允許任何魔法值即未經預先定義的常量直接出現在代碼中。

  • 反例:String key = "Id#taobao_" + tradeId;

2. 【強制】在 long 或者 Long 賦值時,數值後使用大寫的 L,不能是小寫的 l,小寫容易跟數 字 1 混淆,造成誤解。

  •  說明:Long a = 2l; 寫的是數字的 21,還是 Long 型的 2。

3. 【推薦】不要使用一個常量類維護所有常量,要按常量功能進行歸類,分開維護。 

(三) 代碼格式 

2. 【強制】左小括號和字符之間不出現空格同樣,右小括號和字符之間也不出現空格;而左 大括號前需要空格。

  • 反例:if (空格 a == b 空格)

3. 【強制】if/for/while/switch/do 等保留字與括號之間都必須加空格。

4. 【強制】任何二目、三目運算符的左右兩邊都需要加一個空格。 

  • 說明:運算符包括賦值運算符=、邏輯運算符&&、加減乘除符號等。

5. 【強制】採用 4 個空格縮進,禁止使用 tab 字符。 

6. 【強制】註釋的雙斜線與註釋內容之間有且僅有一個空格。 

  • 正例:      // 這是示例註釋,請注意在雙斜線之後有一個空格

 8. 【強制】單行字符數限制不超過 120 個,超出需要換行,換行時遵循如下原則:

  • 1)第二行相對第一行縮進 4 個空格,從第三行開始,不再繼續縮進,參考示例。
  • 2)運算符與下文一起換行。
  • 3)方法調用的點符號與下文一起換行。
  • 4)方法調用中的多個參數需要換行時,在逗號後進行。
  • 5)在括號前不要換行,如sb.append("Jack").append("Ma")...append

                                                  ("alibaba");   // 超過 120 個字符的情況下,不要在括號前換行

9. 【強制】方法參數在定義和傳入時,多個參數逗號後邊必須加空格。 

  • 正例:method(args1, args2, args3);

11.【推薦】單個方法的總行數不超過 80 行。 

(四) OOP 規約 

3. 【強制】相同參數類型,相同業務含義,纔可以使用 Java 的可變參數,避免使用 Object 

  • 說明:可變參數必須放置在參數列表的最後。(提倡同學們儘量不用可變參數編程)

5. 【強制】不能使用過時的類或方法。  

6. 【強制】Object equals 方法容易拋空指針異常,應使用常量或確定有值的對象來調用 equals。

  • 正例:"test".equals(object);
  • 反例:object.equals("test");

7. 【強制】所有整型包裝類對象之間值的比較,全部使用 equals 方法比較。 

8. 【強制】浮點數之間的等值判斷,基本數據類型不能用==來比較,包裝數據類型不能用 equals 來判斷。

  •  反例:

  •  正例:

 10.【強制】爲了防止精度損失,禁止使用構造方法 BigDecimal(double)的方式把 double 值轉 化爲 BigDecimal 對象

  • 說明:BigDecimal(double)存在精度損失風險,在精確計算或值比較的場景中可能會導致業務邏輯異常。
  • 正例:優先推薦入參爲 String 的構造方法,或使用 BigDecimal 的 valueOf 方法,此方法內部其實執行了Double 的 toString,而 Double 的 toString 按 double 的實際能表達的精度對尾數進行了截斷。                                     BigDecimal recommend1 = new BigDecimal("0.1");                                                                                               BigDecimal recommend2 = BigDecimal.valueOf(0.1);

11.關於基本數據類型與包裝數據類型的使用標準如下: 

  • 1) 【強制】所有的 POJO 類屬性必須使用包裝數據類型。
  • 2) 【強制】RPC 方法的返回值和參數必須使用包裝數據類型。
  • 3) 【推薦】所有的局部變量使用基本數據類型。

12.【強制】定義 DO/DTO/VO 等 POJO 類時,不要設定任何屬性默認值 

  • 反例:POJO 類的 createTime 默認值爲 new Date(),但是這個屬性在數據提取時並沒有置入具體值,在 更新其它字段時又附帶更新了此字段,導致創建時間被修改成當前時間。

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

16.【強制】禁止在 POJO 類中,同時存在對應屬性 xxx isXxx()getXxx()方法。

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

  • 說明:

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

21.【推薦】循環體內,字符串的連接方式,使用 StringBuilder 的 append 方法進行擴展。 

  • 說明:下例中,反編譯出的字節碼文件顯示每次循環都會 new 出一個 StringBuilder 對象,然後進行 append 操作,最後通過 toString 方法返回 String 對象,造成內存資源浪費。
  • 反例:

24.【推薦】類成員與方法訪問控制從嚴: 

  • 1) 如果不允許外部直接通過 new 來創建對象,那麼構造方法必須是 private。
  • 2) 工具類不允許有 public 或 default 構造方法。
  • 3) 類非 static 成員變量並且與子類共享,必須是 protected。
  • 4) 類非 static 成員變量並且僅在本類使用,必須是 private。
  • 5) 類 static 成員變量如果僅在本類使用,必須是 private。
  • 6) 若是 static 成員變量,考慮是否爲 final。
  • 7) 類成員方法只供類內部調用,必須是 private。
  • 8) 類成員方法只對繼承類公開,那麼限制爲 protected。
  • 說明:任何類、方法、參數、變量,嚴控訪問範圍。過於寬泛的訪問範圍,不利於模塊解耦。

(五) 集合處理 

1. 【強制】關於 hashCode 和 equals 的處理,遵循如下規則:

  • 1) 只要覆寫 equals,就必須覆寫 hashCode。
  • 2) 因爲 Set 存儲的是不重複的對象,依據 hashCode 和 equals 進行判斷,所以 Set 存儲的對象必須覆寫這兩個方法。
  • 3) 如果自定義對象作爲 Map 的鍵,那麼必須覆寫 hashCode 和 equals。
  • 說明:String 已覆寫 hashCode 和 equals 方法,所以我們可以愉快地使用 String 對象作爲 key 來使用。

3. 【強制】使用 Map 的方法 keySet()/values()/entrySet()返回集合對象時,不可以對其進行添 加元素操作,否則會拋出 UnsupportedOperationException 異常。

6. 【強制】使用集合轉數組的方法,必須使用集合的 toArray(T[] array),傳入的是類型完全一 致、長度爲 0 的空數組。

  • 反例:直接使用 toArray 無參方法存在問題,此方法返回值只能是 Object[]類,若強轉其它類型數組將出現ClassCastException 錯誤。

7. 【強制】在使用 Collection 接口任何實現類的 addAll()方法時,都要對輸入的集合參數進行 NPE 判斷。

  •  說明:在 ArrayList#addAll 方法的第一行代碼即 Object[] a = c.toArray(); 其中 c 爲輸入集合參數,如果 爲 null,則直接拋出異常。

8. 【強制】使用工具類 Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方 法,它的 add/remove/clear 方法會拋出 UnsupportedOperationException 異常。

  •  說明:asList 的返回對象是一個 Arrays 內部類,並沒有實現集合的修改方法。Arrays.asList 體現的是適 配器模式,只是轉換接口,後臺的數據仍是數組。

11.【強制】不要在 foreach 循環裏進行元素的 remove/add 操作。remove 元素請使用 Iterator 方式,如果併發操作,需要對 Iterator 對象加鎖。

反例:

15.【推薦】使用 entrySet 遍歷 Map 類集合 KV,而不是 keySet 方式進行遍歷。

  • 說明:keySet 其實是遍歷了 2 次,一次是轉爲 Iterator 對象,另一次是從 hashMap 中取出 key 所對應 的 value。而 entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8, 使用 Map.forEach 方法。

16.【推薦】高度注意 Map 類集合 K/V 能不能存儲 null 值的情況,如下表格: 

                集合類

            Key

           Value

          Super                           說明

Hashtable

不允許爲 null

不允許爲 null

Dictionary

線程安全

ConcurrentHashMap

不允許爲 null

不允許爲 null

AbstractMap

鎖分段技術(JDK8:CAS)

TreeMap

不允許爲 null

允許爲 null

AbstractMap

線程不安全

HashMap

允許爲 null

允許爲 null

AbstractMap

線程不安全

(六) 併發處理

2. 【強制】創建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。

3. 【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。 

  • 說明:線程池的好處是減少在創建和銷燬線程上所消耗的時間以及系統資源的開銷,解決資源不足的問 題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。

 4. 【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這 樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。

  • 說明:Executors 返回的線程池對象的弊端如下:
  • 1) FixedThreadPool SingleThreadPool: 允許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
  • 2) CachedThreadPool: 允許的創建線程數量爲 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。

5. 【強制】SimpleDateFormat 是線程不安全的類,一般不要定義爲 static 變量,如果定義爲 static,必須加鎖,或者使用 DateUtils 工具類。

  • 說明:如果是 JDK8 的應用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替 SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable thread-safe。

 6. 【強制】必須回收自定義的 ThreadLocal 變量,尤其在線程池場景下,線程經常會被複用, 如果不清理自定義的 ThreadLocal 變量,可能會影響後續業務邏輯和造成內存泄露等問題。 儘量在代理中使用 try-finally 塊進行回收。

7. 【強制】高併發時,同步調用應該去考量鎖的性能損耗。能用無鎖數據結構,就不要用鎖; 能鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。

  •  說明:儘可能使加鎖的代碼塊工作量儘可能的小,避免在鎖代碼塊中調用 RPC 方法。

8. 【強制】對多個資源、數據庫表、對象同時加鎖時,需要保持一致的加鎖順序,否則可能會 造成死鎖。

  •  說明:線程一需要對錶 A、B、C 依次全部加鎖後纔可以進行更新操作,那麼線程二的加鎖順序也必須是 A、B、C,否則可能出現死鎖。

11.【強制】併發修改同一記錄時,避免更新丟失,需要加鎖。要麼在應用層加鎖,要麼在緩存 加鎖,要麼在數據庫層使用樂觀鎖,使用 version 作爲更新依據。

  •  說明:如果每次訪問衝突概率小於 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數不得小於 3 次。

13.【推薦】資金相關的金融敏感信息,使用悲觀鎖策略。 

17.【參考】volatile 解決多線程內存不可見問題。對於一寫多讀,是可以解決變量同步問題,但 是如果多寫,同樣無法解決線程安全問題。

  • 說明:如果是 count++操作,使用如下類實現:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推薦使用 LongAdder 對象,比 AtomicLong 性能更好(減少樂觀 鎖的重試次數)。

 18.【參考】HashMap 在容量不夠進行 resize 時由於高併發可能出現死鏈,導致 CPU 飆升,在 開發過程中可以使用其它數據結構或加鎖來規避此風險。

(七) 控制語句 

1. 【強制】在一個 switch 塊內,每個 case 要麼通過 continue/break/return 等來終止,要麼 註釋說明程序將繼續執行到哪一個 case 爲止;在一個 switch 塊內,都必須包含一個 default 語句並且放在最後,即使它什麼代碼也沒有。

2. 【強制】當 switch 括號內的變量類型爲 String 並且此變量爲外部參數時,必須先進行 null 判斷。 

4. 【強制】在高併發場景中,避免使用”等於”判斷作爲中斷或退出的條件。 

  • 說明:如果併發控制沒有處理好,容易產生等值判斷被“擊穿”的情況,使用大於或小於的區間判斷條件 來代替。
  • 反例:判斷剩餘獎品數量等於 0 時,終止發放獎品,但因爲併發處理錯誤導致獎品數量瞬間變成了負數, 這樣的話,活動無法終止。

5. 【推薦】表達異常的分支時,少用 if-else 方式,這種方式可以改寫成: 

  • 說明:如果非使用 if()...else if()...else...方式表達邏輯,避免後續代碼維護困難,【強制】請勿超過 3 層。

6. 【推薦】除常用方法(如 getXxx/isXxx)等外,不要在條件判斷中執行其它複雜的語句,將復 雜邏輯判斷的結果賦值給一個有意義的布爾變量名,以提高可讀性。

  • 說明:很多 if 語句內的邏輯表達式相當複雜,與、或、取反混合運算,甚至各種方法縱深調用,理解成 本非常高。如果賦值一個非常好理解的布爾變量名字,則是件令人爽心悅目的事情。
  • 正例:
// 僞代碼如下
final boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed) {
...
}
  • 反例:
public final void acquire(long arg) {
if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
    selfInterrupt();
} }

9. 【推薦】避免採用取反邏輯運算符。

  • 說明:取反邏輯不利於快速理解,並且取反邏輯寫法必然存在對應的正向邏輯寫法。
  • 正例:使用 if (x < 628) 來表達 x 小於 628。
  • 反例:使用 if (!(x >= 628)) 來表達 x 小於 628。

(八) 註釋規約  

1. 【強制】類、類屬性、類方法的註釋必須使用 Javadoc 規範,使用/**內容*/格式,不得使用 // xxx 方式。

2. 【強制】所有的抽象方法包括接口中的方法必須要用 Javadoc 註釋、除了返回值、參數、 異常說明外,還必須指出該方法做什麼事情,實現什麼功能。

3. 【強制】所有的類都必須添加創建者和創建日期。

4. 【強制】方法內部單行註釋,在被註釋語句上方另起一行,使用//註釋。方法內部多行註釋 使用/* */註釋,注意與代碼對齊。

5. 【強制】所有的枚舉類型字段必須要有註釋,說明每個數據項的用途。

8. 【參考】謹慎註釋掉代碼。在上方詳細說明,而不是簡單地註釋掉。如果無用,則刪除。 

  • 說明:代碼被註釋掉有兩種可能性:1)後續會恢復此段代碼邏輯。2)永久不用。前者如果沒有備註信 息,難以知曉註釋動機。後者建議直接刪掉(代碼倉庫已然保存了歷史代碼)。

(九) 其它 

1. 【強制】在使用正則表達式時,利用好其預編譯功能,可以有效加快正則匹配速度。

  • 說明:不要在方法體內定義:Pattern pattern = Pattern.compile(“規則”);

5. 【強制】獲取當前毫秒數 System.currentTimeMillis(); 而不是 new Date().getTime(); 

  • 說明:如果想獲取更加精確的納秒級時間值,使用 System.nanoTime()的方式。在 JDK8 中,針對統計時 間等場景,推薦使用 Instant 類。

二、異常日誌 

(一) 異常處理

2. 【強制】異常不要用來做流程控制,條件控制。

  • 說明:異常設計的初衷是解決程序運行中的各種意外情況,且異常的處理效率比條件判斷方式要低很多。

4. 【強制】捕獲異常是爲了處理它,不要捕獲了卻什麼都不處理而拋棄之,如果不想處理它, 請將該異常拋給它的調用者。最外層的業務使用者,必須處理異常,將其轉化爲用戶可以理 解的內容。

7. 【強制】不要在 finally 塊中使用 return

  • 說明:try 塊中的 return 語句執行成功後,並不馬上返回,而是繼續執行 finally 塊中的語句,如果此處存 在 return 語句,則在此直接返回,無情丟棄掉 try 塊中的返回點。

11.【推薦】防止 NPE,是程序員的基本修養,注意 NPE 產生的場景:  

  • 1) 返回類型爲基本數據類型,return 包裝數據類型的對象時,自動拆箱有可能產生 NPE。                                                  反例:public int f() { return Integer 對象}, 如果爲 null,自動解箱拋 NPE。
  • 2) 數據庫的查詢結果可能爲 null。
  • 3) 集合裏的元素即使 isNotEmpty,取出的數據元素也可能爲 null。
  • 4) 遠程調用返回對象時,一律要求進行空指針判斷,防止 NPE。
  • 5) 對於 Session 中獲取的數據,建議進行 NPE 檢查,避免空指針。
  • 6) 級聯調用 obj.getA().getB().getC();一連串調用,易產生 NPE。                                                                                      正例:使用 JDK8 的 Optional 類來防止 NPE 問題。

(二) 日誌規約 

1. 【強制】應用中不可直接使用日誌系統(Log4jLogback)中的 API,而應依賴使用日誌框架 SLF4J 中的 API,使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式統一。

4. 【強制】在日誌輸出時,字符串變量之間的拼接使用佔位符的方式。

  • 說明:因爲 String 字符串的拼接會使用 StringBuilder append()方式,有一定的性能損耗。使用佔位符 僅是替換動作,可以有效提升性能。
  • 正例:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);

6. 【強制】避免重複打印日誌,浪費磁盤空間,務必在 log4j.xml 中設置 additivity=false 

 三、單元測試

2. 【強制】單元測試應該是全自動執行的,並且非交互式的。測試用例通常是被定期執行的, 執行過程必須完全自動化纔有意義。輸出結果需要人工檢查的測試不是一個好的單元測試。 單元測試中不準使用 System.out 來進行人肉驗證,必須使用 assert 來驗證。

3. 【強制】保持單元測試的獨立性。爲了保證單元測試穩定可靠且便於維護,單元測試用例之 間決不能互相調用,也不能依賴執行的先後次序。

  • 反例:method2 需要依賴 method1 的執行,將執行結果作爲 method2 的輸入。

7. 【強制】單元測試代碼必須寫在如下工程目錄:src/test/java,不允許寫在業務代碼目錄下。

  • 說明:源碼編譯時會跳過此目錄,而單元測試框架默認是掃描此目錄。

10.【推薦】對於數據庫相關的查詢,更新,刪除等操作,不能假設數據庫裏的數據是存在的, 或者直接操作數據庫把數據插入進去,請使用程序插入或者導入數據的方式來準備數據。

  • 反例:刪除某一行數據的單元測試,在數據庫中,先直接手動增加一行作爲刪除目標,但是這一行新增數 據並不符合業務插入規則,導致測試結果異常。

11.【推薦】和數據庫相關的單元測試,可以設定自動回滾機制,不給數據庫造成髒數據。或者 對單元測試產生的數據有明確的前後綴標識

四、安全規約  

1. 【強制】隸屬於用戶個人的頁面或者功能必須進行權限控制校驗。

  • 說明:防止沒有做水平權限校驗就可隨意訪問、修改、刪除別人的數據,比如查看他人的私信內容、修改 他人的訂單。

2. 【強制】用戶敏感數據禁止直接展示,必須對展示數據進行脫敏。 

  • 說明:中國大陸個人手機號碼顯示爲:137****0969,隱藏中間 4 位,防止隱私泄露。

4. 【強制】用戶請求傳入的任何參數必須做有效性驗證。 

8. 【推薦】發貼、評論、發送即時消息等用戶生成內容的場景必須實現防刷、文本內容違禁詞 過濾等風控策略。

五、MySQL 數據庫  

(一) 建表規約

1. 【強制】表達是與否概念的字段,必須使用 is_xxx 的方式命名,數據類型是 unsigned tinyint(1 表示是,0 表示否)。

2. 【強制】表名、字段名必須使用小寫字母或數字禁止出現數字開頭,禁止兩個下劃線中間 只出現數字。數據庫字段名的修改代價很大,因爲無法進行預發佈,所以字段名稱需要慎重 考慮。

  • 說明:MySQL 在 Windows 下不區分大小寫,但在 Linux 下默認是區分大小寫。因此,數據庫名、表 名、字段名,都不允許出現任何大寫字母,避免節外生枝。

3. 【強制】表名不使用複數名詞。 

5. 【強制】主鍵索引名爲 pk_字段名;唯一索引名爲 uk_字段名普通索引名則爲 idx_字段名。

  • 說明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的簡稱。

6. 【強制】小數類型爲 decimal,禁止使用 float double

  • 說明:在存儲的時候,float 和 double 都存在精度損失的問題,很可能在比較值的時候,得到不正確的 結果。如果存儲的數據範圍超過 decimal 的範圍,建議將數據拆成整數和小數並分開存儲。

7. 【強制】如果存儲的字符串長度幾乎相等,使用 char 定長字符串類型。

8. 【強制】varchar 是可變長字符串,不預先分配存儲空間,長度不要超過 5000,如果存儲長 度大於此值,定義字段類型爲 text,獨立出來一張表,用主鍵來對應,避免影響其它字段索 引效率。

 9. 【強制】表必備三字段:id, create_time, update_time。

  • 說明:其中 id 必爲主鍵,類型爲 bigint unsigned、單表時自增、步長爲 1。create_time, update_time 的類型均爲 datetime 類型。

11.【推薦】庫名與應用名稱儘量一致。 

14.【推薦】單錶行數超過 500 萬行或者單表容量超過 2GB,才推薦進行分庫分表。

  • 說明:如果預計三年後的數據量根本達不到這個級別,請不要在創建表時就分庫分表。

 (二) 索引規約

1. 【強制】業務上具有唯一特性的字段,即使是多個字段的組合,也必須建成唯一索引。

說明:不要以爲唯一索引影響了 insert 速度,這個速度損耗可以忽略,但提高查找速度是明顯的;另外, 即使在應用層做了非常完善的校驗控制,只要沒有唯一索引,根據墨菲定律,必然有髒數據產生。

2. 【強制】超過三個表禁止 join。需要 join 的字段,數據類型必須絕對一致多表關聯查詢 時,保證被關聯的字段需要有索引。

  • 說明:即使雙表 join 也要注意表索引、SQL 性能。

3. 【強制】varchar 字段上建立索引時,必須指定索引長度,沒必要對全字段建立索引,根據 實際文本區分度決定索引長度即可。

5. 【推薦】如果有 order by 的場景,請注意利用索引的有序性。order by 最後的字段是組合 索引的一部分,並且放在索引組合順序的最後,避免出現 file_sort 的情況,影響查詢性能。

4. 【強制】頁面搜索嚴禁左模糊或者全模糊,如果需要請走搜索引擎來解決。

  • 說明:索引文件具有 B-Tree 的最左前綴匹配特性,如果左邊的值未確定,那麼無法使用此索引。

7. 【推薦】利用延遲關聯或者子查詢優化超多分頁場景。

  • 說明:MySQL 並不是跳過 offset 行,而是取 offset+N 行,然後返回放棄前 offset 行,返回 N 行,那當 offset 特別大的時候,效率就非常的低下,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行 SQL 改寫。
  • 正例:先快速定位需要獲取的 id 段,然後再關聯:                                                                                                                      SELECT a.* FROM 表 1 a, (select id from 表 1 where 條件 LIMIT 100000,20 ) b where a.id=b.id

 (三) SQL 語句

1. 【強制】不要使用 count(列名)或 count(常量)來替代 count(*),count(*)是 SQL92 定義的

標準統計行數的語法,跟數據庫無關,跟 NULL 和非 NULL 無關。

說明:count(*)會統計值爲 NULL 的行,而 count(列名)不會統計此列爲 NULL 值的行。

2. 【強制】count(distinct col) 計算該列除 NULL 之外的不重複行數,注意 count(distinct col1, col2) 如果其中一列全爲 NULL,那麼即使另一列有不同的值,也返回爲 0。

3. 【強制】當某一列的值全是 NULL 時,count(col)的返回結果爲 0,但 sum(col)的返回結果 爲 NULL,因此使用 sum()時需注意 NPE 問題。

  • 正例:使用如下方式來避免 sum 的 NPE 問題:SELECT IFNULL(SUM(column), 0) FROM table;

4. 【強制】使用 ISNULL()來判斷是否爲 NULL 值。 

7. 【強制】禁止使用存儲過程,存儲過程難以調試和擴展,更沒有移植性。 

8. 【強制】數據訂正(特別是刪除、修改記錄操作)時,要先 select,避免出現誤刪除,確認無 誤才能執行更新語句。

9. 【推薦】in 操作能避免則避免,若實在避免不了,需要仔細評估 in 後邊的集合元素數量,控 制在 1000 個之內。

(四) ORM 映射 

1. 【強制】在表查詢中,一律不要使用 * 作爲查詢的字段列表,需要哪些字段必須明確寫明。

說明:1)增加查詢分析器解析成本。2)增減字段容易與 resultMap 配置不一致。3)無用字段增加網絡 消耗,尤其是 text 類型的字段。

2. 【強制】POJO 類的布爾屬性不能加 is,而數據庫字段必須加 is_,要求在 resultMap 中進行 字段與屬性之間的映射。

說明:參見定義 POJO 類以及數據庫字段定義規定,在<resultMap>中增加映射,是必須的。在 MyBatis Generator 生成的代碼中,需要進行對應的修改。

6. 【強制】不允許直接拿 HashMap Hashtable 作爲查詢結果集的輸出。

  • 說明:resultClass=”Hashtable”,會置入字段名和屬性值,但是值的類型不可控。

觀閱原文件可免費下載java開發手冊(阿里巴巴)

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