一、 編程規約 (一) 命名風格
6. 【強制】抽象類命名使用Abstract或Base開頭;異常類命名使用Exception 結尾;測試類 命名以它要測試的類的名稱開始,以Test結尾。
-
【強制】POJO類中布爾類型變量都不要加is前綴,否則部分框架解析會引起序列化錯誤。 說明:在本文 MySQL 規約中的建表約定第一條,表達是與否的值采用 is_xxx 的命名方式,所以,需要在
設置從 is_xxx到 xxx的映射關系。 反例:定義為基本數據類型 Boolean isDeleted 的屬性,它的方法也是 isDeleted(),RPC框架在反向解 析的時候,“誤以為”對應的屬性名稱是 deleted,導致屬性獲取不到,進而拋出異常。 -
【參考】各層命名規約: A) Service/DAO 層方法命名規約 1) 獲取單個對象的方法用 get 做前綴。 2) 獲取多個對象的方法用 list 做前綴,復數形式結尾如:listObjects。 3) 獲取統計值的方法用 count 做前綴。 4) 插入的方法用save/insert 做前綴。 5) 刪除的方法用remove/delete 做前綴。 6) 修改的方法用update 做前綴。 B) 領域模型命名規約 1) 數據對象:xxxDO,xxx 即為數據表名。 2) 數據傳輸對象:xxxDTO,xxx為業務領域相關的名稱。 3) 展示對象:xxxVO,xxx一般為網頁名稱。 4) POJO是 DO/DTO/BO/VO的統稱,禁止命名成 xxxPOJO。
(三) 代碼格式
5. 【強制】采用4個空格縮進,禁止使用tab字符。 說明:如果使用 tab 縮進,必須設置1 個tab 為4 個空格。IDEA 設置 tab 為4 個空格時,請勿勾選 Use tab character;而在 eclipse 中,必須勾選 insert spaces for tabs。 正例: (涉及1-5 點)
public static void main(String[] args) { // 縮進4個空格 String say = "hello"; // 運算符的左右必須有一個空格 int flag = 0; // 關鍵詞if與括號之間必須有一個空格,括號內的f與左括號,0與右括號不需要空格 if (flag == 0) { System.out.println(say); } // 左大括號前加空格且不換行;左大括號后換行 if (flag == 1) { System.out.println("world"); // 右大括號前換行,右大括號后有else,不用換行 } else { System.out.println("ok"); // 在右大括號后直接結束,則必須換行 } }
(四) OOP規約
-
【強制】避免通過一個類的對象引用訪問此類的靜態變量或靜態方法,無謂增加編譯器解析 成本,直接用類名來訪問即可。
-
【強制】浮點數之間的等值判斷,基本數據類型不能用==來比較,包裝數據類型不能用 equals來判斷。 說明:浮點數采用“尾數+階碼”的編碼方式,類似於科學計數法的“有效數字+指數”的表示方式。二進制無法精確表示大部分的十進制小數
-
【強制】為了防止精度損失,禁止使用構造方法 BigDecimal(double)的方式把double值轉 化為 BigDecimal對象。
-
關於基本數據類型與包裝數據類型的使用標准如下: 1) 【強制】所有的 POJO類屬性必須使用包裝數據類型。 2) 【強制】RPC 方法的返回值和參數必須使用包裝數據類型。 3) 【推薦】所有的局部變量使用基本數據類型。 說明:POJO類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何 NPE問題,或 者入庫檢查,都由使用者來保證。 正例:數據庫的查詢結果可能是 null,因為自動拆箱,用基本數據類型接收有 NPE 風險。 反例:比如顯示成交總額漲跌情況,即正負 x%,x為基本數據類型,調用的 RPC 服務,調用不成功時, 返回的是默認值,頁面顯示為 0%,這是不合理的,應該顯示成中划線。所以包裝數據類型的 null值,能 夠表示額外的信息,如:遠程調用失敗,異常退出。
-
【強制】POJO類必須寫toString方法。使用IDE中的工具:source> generate toString 時,如果繼承了另一個POJO類,注意在前面加一下super.toString。 說明:在方法執行拋出異常時,可以直接調用 POJO的 toString()方法打印其屬性值,便於排查問題。
-
【強制】禁止在POJO類中,同時存在對應屬性xxx的isXxx()和getXxx()方法。 說明:框架在調用屬性 xxx的提取方法時,並不能確定哪個方法一定是被優先調用到。
-
【推薦】使用索引訪問用String的split方法得到的數組時,需做最后一個分隔符后有無內 容的檢查,否則會有拋IndexOutOfBoundsException 的風險。 說明: String str = "a,b,c,,"; String[] ary = str.split(","); // 預期大於3,結果是3 System.out.println(ary.length);
-
【推薦】循環體內,字符串的連接方式,使用StringBuilder的append方法進行擴展。 說明:下例中,反編譯出的字節碼文件顯示每次循環都會 new出一個 StringBuilder 對象,然后進行 append 操作,最后通過 toString 方法返回 String 對象,造成內存資源浪費。 反例: String str = "start"; for (int i = 0; i < 100; i++) { str = str + "hello"; }
-
【推薦】final可以聲明類、成員變量、方法、以及本地變量,下列情況使用 final關鍵字: 1) 不允許被繼承的類,如:String 類。 2) 不允許修改引用的域對象。 3) 不允許被覆寫的方法,如:POJO類的 setter 方法。 4) 不允許運行過程中重新賦值的局部變量。 5) 避免上下文重復使用一個變量,使用 final可以強制重新定義一個變量,方便更好地進行重構。
-
【推薦】慎用 Object的 clone方法來拷貝對象。 說明:對象clone 方法默認是淺拷貝,若想實現深拷貝需覆寫clone 方法實現域對象的深度遍歷式拷貝。
-
【推薦】類成員與方法訪問控制從嚴: 1) 如果不允許外部直接通過 new來創建對象,那么構造方法必須是 private。 2) 工具類不允許有 public或default 構造方法。 3) 類非static 成員變量並且與子類共享,必須是 protected。 4) 類非static 成員變量並且僅在本類使用,必須是private。 5) 類static 成員變量如果僅在本類使用,必須是 private。 6) 若是static 成員變量,考慮是否為final。 7) 類成員方法只供類內部調用,必須是 private。 8) 類成員方法只對繼承類公開,那么限制為 protected。
(五) 集合處理
-
【強制】關於hashCode和equals的處理,遵循如下規則: 1) 只要覆寫equals,就必須覆寫hashCode。 2) 因為Set 存儲的是不重復的對象,依據 hashCode和equals進行判斷,所以 Set 存儲的對象必須覆 寫這兩個方法。 3) 如果自定義對象作為Map 的鍵,那么必須覆寫hashCode 和equals。 說明:String 已覆寫hashCode 和equals方法,所以我們可以愉快地使用 String 對象作為key來使用。
-
【強制】使用Map 的方法 keySet()/values()/entrySet()返回集合對象時,不可以對其進行添 加元素操作,否則會拋出UnsupportedOperationException 異常。
說明:entrySet()中有key和value,所以直接加入元素或者刪除元素的方法都是無效的。keySet()中有key,可以對key進行操作,所以能使用remove()和equals(),所以返回true。
values()中只有value值,沒有key,value值是沒什么用的,所以values()方法也僅僅是獲取所有value值方便。 -
【強制】Collections 類返回的對象,如:emptyList()/singletonList()等都是immutable list,不可對其進行添加或者刪除元素的操作。 反例:如果查詢無結果,返回 Collections.emptyList()空集合對象,調用方一旦進行了添加元素的操作,就 會觸發 UnsupportedOperationException 異常。
-
【強制】在subList場景中,高度注意對原集合元素的增加或刪除,均會導致子列表的遍 歷、增加、刪除產生ConcurrentModificationException 異常。
正例:String[] sids = sList.toArray(new String[sList.size()]);
Listlist = new ArrayList<>(2); list.add("guan"); list.add("bao"); String[] array = list.toArray(new String[0]);
說明:使用 toArray 帶參方法,數組空間大小的 length: 1) 等於 0,動態創建與 size 相同的數組,性能最好。 2) 大於 0 但小於size,重新創建大小等於 size 的數組,增加 GC負擔。
Java 開發手冊
12/44
3) 等於 size,在高並發情況下,數組創建完成之后,size 正在變大的情況下,負面影響與上相同。 4) 大於 size,空間浪費,且在size 處插入 null 值,存在 NPE隱患。
反例:String[] array= (String[]) list.toArray();運行,報錯
-
【強制】使用工具類Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方 法,它的add/remove/clear方法會拋出UnsupportedOperationException 異常。 說明:asList 的返回對象是一個 Arrays內部類,並沒有實現集合的修改方法。Arrays.asList 體現的是適 配器模式,只是轉換接口,后台的數據仍是數組。 String[] str = new String[] { "yang", "hao" }; List list = Arrays.asList(str); 第一種情況:list.add("yangguanbao"); 運行時異常。 第二種情況:str[0] = "changed"; 也會隨之修改,反之亦然。
-
【強制】泛型通配符<? extends T>來接收返回的數據,此寫法的泛型集合不能使用 add方 法,而<? super T>不能使用 get方法,作為接口調用賦值時易出錯。
說明:
extends 可用於返回類型限定,不能用於參數類型限定(換句話說:? extends xxx 只能用於方法返回類型限定,jdk能夠確定此類的最小繼承邊界為xxx,只要是這個類的父類都能接收,但是傳入參數無法確定具體類型,只能接受null的傳入)。
super 可用於參數類型限定,不能用於返回類型限定(換句話說:? supper xxx 只能用於方法傳參,因為jdk能夠確定傳入為xxx的子類,返回只能用Object類接收)。
? 既不能用於方法參數傳入,也不能用於方法返回。 -
【強制】不要在foreach循環里進行元素的remove/add操作。remove元素請使用 Iterator方式,如果並發操作,需要對Iterator對象加鎖。
正例:
List
list.add("1");
list.add("2");
Iterator
while (iterator.hasNext()) {
String item = iterator.next();
if (刪除元素的條件) {
iterator.remove();
}
}
調用iterator的刪除方法:
1.首先檢查集合
2.刪除元素
3.下一個元素的索引位置cursor重新賦值
4.檢查集合參數重新賦值
反例:
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
總結:如果我們我們用foreach刪除的元素剛好是最后一個,刪除完成前下一個元素的索引位置cursor剛好等於集合長度size的大小。但是,刪除完成后size的數量減1,但是cursor並沒有變化。導致下一次循環不相等繼續向下執行,導致檢查數組不通過,拋出java.util.ConcurrentModificationException
-
【強制】在 JDK7版本及以上,Comparator實現類要滿足如下三個條件,不然 Arrays.sort, Collections.sort會拋 IllegalArgumentException異常。 說明:三個條件如下 1) x,y的比較結果和 y,x的比較結果相反。 2) x>y,y>z,則x>z。 3) x=y,則x,z 比較結果和 y,z 比較結果相同。
反例:下例中沒有處理相等的情況,交換兩個對象判斷結果並不互反,不符合第一個條件,在實際使用中
可能會出現異常。
new Comparator() {
@Override
public int compare(Student o1, Student o2) {
return o1.getId() > o2.getId() ? 1 : -1;
}
};15. 【推薦】使用 entrySet遍歷 Map類集合 KV,而不是 keySet方式進行遍歷。 說明:keySet 其實是遍歷了2 次,一次是轉為 Iterator 對象,另一次是從 hashMap 中取出key所對應 的 value。而entrySet 只是遍歷了一次就把 key和value 都放到了 entry中,效率更高。如果是 JDK8, 使用 Map.forEach 方法。
正例:values()返回的是 V值集合,是一個 list 集合對象;keySet()返回的是K 值集合,是一個 Set 集合 對象;entrySet()返回的是K-V值組合集合。
-
【參考】合理利用好集合的有序性(sort)和穩定性(order),避免集合的無序性(unsort)和不穩 定性(unorder)帶來的負面影響。 說明:有序性是指遍歷的結果是按某種比較規則依次排列的。穩定性指集合每次遍歷的元素次序是一定 的。如:ArrayList 是order/unsort;HashMap 是unorder/unsort;TreeSet是 order/sort。
-
【參考】利用Set元素唯一的特性,可以快速對一個集合進行去重操作,避免使用List的 contains方法進行遍歷、對比、去重操作。
(六) 並發處理
3. 【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。 說明:線程池的好處是減少在創建和銷毀線程上所消耗的時間以及系統資源的開銷,解決資源不足的問 題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。
-
【強制】線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這 樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。 說明:Executors返回的線程池對象的弊端如下: 1) FixedThreadPool和SingleThreadPool: 允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。 2) CachedThreadPool: 允許的創建線程數量為 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。
-
【強制】SimpleDateFormat 是線程不安全的類,一般不要定義為 static變量,如果定義為 static,必須加鎖,或者使用 DateUtils工具類。 正例:注意線程安全,使用 DateUtils。亦推薦如下處理: private static final ThreadLocal
df = new ThreadLocal () { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } }; 說明:如果是JDK8 的應用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable thread-safe。 -
【強制】必須回收自定義的ThreadLocal變量,尤其在線程池場景下,線程經常會被復用, 如果不清理自定義的 ThreadLocal變量,可能會影響后續業務邏輯和造成內存泄露等問題。 盡量在代理中使用try-finally塊進行回收。 正例: objectThreadLocal.set(userInfo); try { // ... } finally { objectThreadLocal.remove(); }
-
【強制】高並發時,同步調用應該去考量鎖的性能損耗。能用無鎖數據結構,就不要用鎖; 能鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。 說明:盡可能使加鎖的代碼塊工作量盡可能的小,避免在鎖代碼塊中調用 RPC方法
-
【強制】在使用阻塞等待獲取鎖的方式中,必須在try 代碼塊之外,並且在加鎖方法與try 代 碼塊之間沒有任何可能拋出異常的方法調用,避免加鎖成功后,在 finally 中無法解鎖。 說明一:如果在 lock 方法與 try代碼塊之間的方法調用拋出異常,那么無法解鎖,造成其它線程無法成功 獲取鎖。 說明二:如果lock 方法在try代碼塊之內,可能由於其它方法拋出異常,導致在 finally代碼塊中, unlock 對未加鎖的對象解鎖,它會調用AQS的tryRelease 方法(取決於具體實現類),拋出 IllegalMonitorStateException 異常。 說明三:在Lock 對象的lock 方法實現中可能拋出 unchecked 異常,產生的后果與說明二相同
-
【強制】在使用嘗試機制來獲取鎖的方式中,進入業務代碼塊之前,必須先判斷當前線程是 否持有鎖。鎖的釋放規則與鎖的阻塞等待方式相同。 說明:Lock 對象的 unlock 方法在執行時,它會調用 AQS的 tryRelease 方法(取決於具體實現類),如果 當前線程不持有鎖,則拋出 IllegalMonitorStateException 異常。
-
【強制】並發修改同一記錄時,避免更新丟失,需要加鎖。要么在應用層加鎖,要么在緩存 加鎖,要么在數據庫層使用樂觀鎖,使用 version作為更新依據。 說明:如果每次訪問沖突概率小於 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數不得小於 3 次。
-
【強制】多線程並行處理定時任務時,Timer運行多個 TimeTask時,只要其中之一沒有捕獲 拋出的異常,其它任務便會自動終止運行,如果在處理定時任務時使用 ScheduledExecutorService 則沒有這個問題。
-
【推薦】使用 CountDownLatch進行異步轉同步操作,每個線程退出前必須調用 countDown 方法,線程執行代碼注意 catch異常,確保 countDown方法被執行到,避免主線程無法執行 至 await方法,直到超時才返回結果。 說明:注意,子線程拋出異常堆棧,不能在主線程 try-catch 到。
-
【推薦】避免 Random實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一 seed 導致的性能下降。 說明:Random實例包括java.util.Random 的實例或者 Math.random()的方式。 正例:在JDK7 之后,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前,需要編碼保證每個線 程持有一個實例。
-
【推薦】在並發場景下,通過雙重檢查鎖(double-checked locking)實現延遲初始化的優化 問題隱患(可參考 The "Double
-
【參考】volatile解決多線程內存不可見問題。對於一寫多讀,是可以解決變量同步問題,但 是如果多寫,同樣無法解決線程安全問題。 說明:如果是count++操作,使用如下類實現:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是JDK8,推薦使用LongAdder 對象,比 AtomicLong 性能更好(減少樂觀 鎖的重試次數)。
-
【參考】HashMap 在容量不夠進行resize時由於高並發可能出現死鏈,導致CPU飆升,在 開發過程中可以使用其它數據結構或加鎖來規避此風險。
-
【參考】ThreadLocal對象使用static修飾,ThreadLocal 無法解決共享對象的更新問題。 說明:這個變量是針對一個線程內所有操作共享的,所以設置為靜態變量,所有此類實例共享此靜態變 量,也就是說在類第一次被使用時裝載,只分配一塊存儲空間,所有此類的對象(只要是這個線程內定義 的)都可以操控這個變量。
(七) 控制語句
2. 【強制】當switch 括號內的變量類型為String並且此變量為外部參數時,必須先進行null 判斷。
- 【強制】在高並發場景中,避免使用”等於”判斷作為中斷或退出的條件。 說明:如果並發控制沒有處理好,容易產生等值判斷被“擊穿”的情況,使用大於或小於的區間判斷條件 來代替。 反例:判斷剩余獎品數量等於 0 時,終止發放獎品,但因為並發處理錯誤導致獎品數量瞬間變成了負數, 這樣的話,活動無法終止。
(八) 注釋規約
11. 【參考】特殊注釋標記,請注明標記人與標記時間。注意及時處理這些標記,通過標記掃 描,經常清理此類標記。線上故障有時候就是來源於這些標記處的代碼。 1) 待辦事宜(TODO):(標記人,標記時間,[預計處理時間]) 表示需要實現,但目前還未實現的功能。這實際上是一個 Javadoc 的標簽,目前的 Javadoc 還沒 有實現,但已經被廣泛使用。只能應用於類,接口和方法(因為它是一個 Javadoc 標簽)。 2) 錯誤,不能工作(FIXME):(標記人,標記時間,[預計處理時間]) 在注釋中用FIXME標記某代碼是錯誤的,而且不能工作,需要及時糾正的情況。
(九) 其它
4. 【強制】注意 Math.random() 這個方法返回是 double類型,注意取值的范圍 0≤x<1(能夠 取到零值,注意除零異常),如果想獲取整數類型的隨機數,不要將 x放大10的若干倍然后 取整,直接使用 Random對象的 nextInt或者 nextLong方法。
二、異常日志
(一) 異常處理
5. 【強制】有try塊放到了事務代碼中,catch異常后,如果需要回滾事務,一定要注意手動回 滾事務。
-
【強制】finally塊必須對資源對象、流對象進行關閉,有異常也要做try-catch。 說明:如果JDK7 及以上,可以使用 try-with-resources方式。
-
【強制】不要在finally塊中使用return。 說明:try塊中的 return 語句執行成功后,並不馬上返回,而是繼續執行 finally塊中的語句,如果此處存 在 return 語句,則在此直接返回,無情丟棄掉try塊中的返回點。
-
【推薦】防止 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問題。
(二) 日志規約
6. 【強制】避免重復打印日志,浪費磁盤空間,務必在 log4j.xml中設置 additivity=false。 正例:
-
【推薦】謹慎地記錄日志。生產環境禁止輸出 debug日志;有選擇地輸出 info日志;如果使 用 warn來記錄剛上線時的業務行為信息,一定要注意日志輸出量的問題,避免把服務器磁盤 撐爆,並記得及時刪除這些觀察日志。 說明:大量地輸出無效日志,不利於系統性能提升,也不利於快速定位錯誤點。記錄日志時請思考:這些 日志真的有人看嗎?看到這條日志你能做什么?能不能給問題排查帶來好處?
-
【推薦】可以使用warn日志級別來記錄用戶輸入參數錯誤的情況,避免用戶投訴時,無所 適從。如非必要,請不要在此場景打出error級別,避免頻繁報警。 說明:注意日志輸出的級別,error 級別只記錄系統邏輯出錯、異常或者重要的錯誤信息。
-
【推薦】盡量用英文來描述日志錯誤信息,如果日志中的錯誤信息用英文描述不清楚的話使 用中文描述即可,否則容易產生歧義。
三、單元測試
-
【強制】好的單元測試必須遵守 AIR原則。 說明:單元測試在線上運行時,感覺像空氣(AIR)一樣並不存在,但在測試質量的保障上,卻是非常關 鍵的。好的單元測試宏觀上來說,具有自動化、獨立性、可重復執行的特點。
⚫ A:Automatic(自動化)
⚫ I:Independent(獨立性)
⚫ R:Repeatable(可重復) -
【推薦】編寫單元測試代碼遵守 BCDE原則,以保證被測試模塊的交付質量。
⚫ B:Border,邊界值測試,包括循環邊界、特殊取值、特殊時間點、數據順序等。
⚫ C:Correct,正確的輸入,並得到預期的結果。
⚫ D:Design,與設計文檔相結合,來編寫單元測試。
⚫ E:Error,強制錯誤信息輸入(如:非法數據、異常流程、業務允許外等),並得到預期的結果。 -
【參考】為了更方便地進行單元測試,業務代碼應避免以下情況:
⚫ 構造方法中做的事情過多。
⚫ 存在過多的全局變量和靜態方法。
⚫ 存在過多的外部依賴。
⚫ 存在過多的條件語句。 說明:多層條件語句建議使用衛語句、策略模式、狀態模式等方式重構。
四、安全規約
-
【強制】隸屬於用戶個人的頁面或者功能必須進行權限控制校驗。 說明:防止沒有做水平權限校驗就可隨意訪問、修改、刪除別人的數據,比如查看他人的私信內容、修改 他人的訂單。
-
【強制】用戶敏感數據禁止直接展示,必須對展示數據進行脫敏。 說明:中國大陸個人手機號碼顯示為:137****0969,隱藏中間 4 位,防止隱私泄露。
-
【強制】用戶輸入的 SQL參數嚴格使用參數綁定或者 METADATA字段值限定,防止 SQL注 入,禁止字符串拼接 SQL訪問數據庫。
-
【強制】用戶請求傳入的任何參數必須做有效性驗證。
-
說明:忽略參數校驗可能導致: ⚫
page size 過大導致內存溢出 ⚫ 惡意order by導致數據庫慢查詢 ⚫ 任意重定向 ⚫ SQL 注入 ⚫ 反序列化注入 ⚫ 正則輸入源串拒絕服務 ReDoS
說明:Java代碼用正則來驗證客戶端的輸入,有些正則寫法驗證普通用戶輸入沒有問題,但是如果攻 擊人員使用的是特殊構造的字符串來驗證,有可能導致死循環的結果。 -
【強制】在使用平台資源,譬如短信、郵件、電話、下單、支付,必須實現正確的防重放的 機制,如數量限制、疲勞度控制、驗證碼校驗,避免被濫刷而導致資損。 說明:如注冊時發送驗證碼到手機,如果沒有限制次數和頻率,那么可以利用此功能騷擾到其它用戶,並 造成短信平台資源浪費。
五、MySQL數據庫
(一) 建表規約
-
【強制】表達是與否概念的字段,必須使用is_xxx的方式命名,數據類型是unsigned tinyint(1表示是,0表示否)。 說明:任何字段如果為非負數,必須是 unsigned。 注意:POJO類中的任何布爾類型的變量,都不要加 is前綴,所以,需要在
設置從 is_xxx 到 Xxx的映射關系。數據庫表示是與否的值,使用 tinyint 類型,堅持 is_xxx的命名方式是為了明確其取 值含義與取值范圍。 正例:表達邏輯刪除的字段名 is_deleted,1 表示刪除,0 表示未刪除。 -
【強制】小數類型為 decimal,禁止使用 float和double。
-
【強制】varchar是可變長字符串,不預先分配存儲空間,長度不要超過5000,如果存儲長 度大於此值,定義字段類型為 text,獨立出來一張表,用主鍵來對應,避免影響其它字段索 引效率。
-
【強制】表必備三字段:id, create_time, update_time。
(二) 索引規約
-
【強制】業務上具有唯一特性的字段,即使是多個字段的組合,也必須建成唯一索引。 說明:不要以為唯一索引影響了 insert 速度,這個速度損耗可以忽略,但提高查找速度是明顯的;另外, 即使在應用層做了非常完善的校驗控制,只要沒有唯一索引,根據墨菲定律,必然有臟數據產生。
-
【強制】超過三個表禁止 join。需要 join的字段,數據類型必須絕對一致;多表關聯查詢 時,保證被關聯的字段需要有索引。 說明:即使雙表 join 也要注意表索引、SQL 性能。
-
【強制】在 varchar字段上建立索引時,必須指定索引長度,沒必要對全字段建立索引,根據 實際文本區分度決定索引長度即可。
說明:索引的長度與區分度是一對矛盾體,一般對字符串類型數據,長度為 20 的索引,區分度會高達 90%以上,可以使用 count(distinct left(列名, 索引長度))/count(*)的區分度來確定。 -
【強制】頁面搜索嚴禁左模糊或者全模糊,如果需要請走搜索引擎來解決。 說明:索引文件具有 B-Tree 的最左前綴匹配特性,如果左邊的值未確定,那么無法使用此索引。
-
【推薦】如果有order by的場景,請注意利用索引的有序性。order by 最后的字段是組合 索引的一部分,並且放在索引組合順序的最后,避免出現file_sort的情況,影響查詢性能。 正例:where a=? and b=? order by c; 索引:a_b_c 反例:索引如果存在范圍查詢,那么索引有序性無法利用,如:WHERE a>10 ORDER BY b; 索引a_b 無 法排序。
-
【推薦】利用覆蓋索引來進行查詢操作,避免回表。 說明:如果一本書需要知道第 11 章是什么標題,會翻開第 11 章對應的那一頁嗎?目錄瀏覽一下就好,這 個目錄就是起到覆蓋索引的作用。 正例:能夠建立索引的種類分為主鍵索引、唯一索引、普通索引三種,而覆蓋索引只是一種查詢的一種效 果,用 explain 的結果,extra列會出現:using index。
-
【推薦】利用延遲關聯或者子查詢優化超多分頁場景。
說明: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語句
-
【強制】不要使用count(列名)或count(常量)來替代count(),count()是SQL92定義的 標准統計行數的語法,跟數據庫無關,跟NULL和非NULL無關。 說明:count(*)會統計值為NULL 的行,而 count(列名)不會統計此列為 NULL 值的行。
-
【強制】count(distinct col) 計算該列除NULL之外的不重復行數,注意 count(distinct col1, col2) 如果其中一列全為NULL,那么即使另一列有不同的值,也返回為0。
-
【強制】當某一列的值全是NULL時,count(col)的返回結果為0,但sum(col)的返回結果 為NULL,因此使用sum()時需注意NPE問題。 正例:使用如下方式來避免 sum的NPE問題:SELECT IFNULL(SUM(column), 0) FROM table;
-
【強制】使用 ISNULL()來判斷是否為 NULL值。 說明:NULL 與任何值的直接比較都為 NULL。 1) NULL<>NULL 的返回結果是 NULL,而不是 false。 2) NULL=NULL 的返回結果是 NULL,而不是true。 3) NULL<>1 的返回結果是 NULL,而不是 true。
-
【強制】代碼中寫分頁查詢邏輯時,若 count為0應直接返回,避免執行后面的分頁語句。
-
【強制】不得使用外鍵與級聯,一切外鍵概念必須在應用層解決。 說明:以學生和成績的關系為例,學生表中的 student_id 是主鍵,那么成績表中的 student_id 則為外 鍵。如果更新學生表中的 student_id,同時觸發成績表中的 student_id 更新,即為級聯更新。外鍵與級 聯更新適用於單機低並發,不適合分布式、高並發集群;級聯更新是強阻塞,存在數據庫更新風暴的風 險;外鍵影響數據庫的插入速度。
-
【強制】禁止使用存儲過程,存儲過程難以調試和擴展,更沒有移植性。
-
【強制】數據訂正(特別是刪除、修改記錄操作)時,要先 select,避免出現誤刪除,確認無 誤才能執行更新語句。
-
【推薦】in操作能避免則避免,若實在避免不了,需要仔細評估 in后邊的集合元素數量,控 制在1000個之內
(四) ORM映射
3. 【強制】不要用resultClass當返回參數,即使所有類屬性名與數據庫字段一一對應,也需要 定義;反過來,每一個表也必然有一個POJO類與之對應。 說明:配置映射關系,使字段與 DO類解耦,方便維護。
-
【強制】sql.xml配置參數使用:#{},#param# 不要使用${} 此種方式容易出現 SQL注入。
-
【強制】不允許直接拿 HashMap與 Hashtable作為查詢結果集的輸出。 說明:resultClass=”Hashtable”,會置入字段名和屬性值,但是值的類型不可控。