宗旨:碼出高效,碼出質量java
(一)命名風格linux
1. 【強制】代碼中的命名均不能如下劃線或美圓符號開始,也不能如下劃線或美圓符號結束程序員
反例:_name / __name / $name / name_ / name$ / name__正則表達式
2. 【強制】代碼中的命名嚴禁使用拼音與英文混合的方式,更不容許直接使用中文的方式spring
注意,即便純拼音命名方式也要避免採用。sql
3. 【強制】類名使用 UpperCamelCase 風格,但如下情形例外:DO / BO / DTO / VO / AO / PO / UID 等。數據庫
正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotionexpress
4. 【強制】方法名、參數名、成員變量、局部變量都統一使用 lowerCamelCase 風格,必須聽從駝峯形式。編程
正例: localValue / getHttpMessage() / inputUserIdjson
5. 【強制】常量命名所有大寫,單詞間用下劃線隔開,力求語義表達完整清楚,不要嫌名字長。
正例:MAX_STOCK_COUNT
反例:MAX_COUNT
6. 【強制】抽象類命名使用 Abstract 或 Base 開頭;異常類命名使用 Exception 結尾;測試類命名以它要測試的類的名稱開始,以 Test 結尾。
7. 【強制】類型與中括號緊挨相連來表示數組。
正例:定義整形數組 int[] arrayDemo;
反例:在 main 參數中,使用 String args[]來定義。
8. 【強制】POJO 類中布爾類型的變量,都不要加 is 前綴,不然部分框架解析會引發序列化錯誤。
反例:定義爲基本數據類型 Boolean isDeleted 的屬性,它的方法也是 isDeleted(),RPC框架在反向解析的時候,「誤覺得」對應的屬性名稱是 deleted,致使屬性獲取不到,進而拋出異常。
9. 【強制】包名統一使用小寫,點分隔符之間有且僅有一個天然語義的英語單詞。包名統一使用單數形式,可是類名若是有複數含義,類名能夠使用複數形式。
正例:應用工具類包名爲 com.alibaba.ai.util、類名爲 MessageUtils(此規則參考 spring的框架結構)
10. 【強制】杜絕徹底不規範的縮寫,避免望文不知義。
反例:AbstractClass「縮寫」命名成 AbsClass;condition「縮寫」命名成 condi,此類隨意縮寫嚴重下降了代碼的可閱讀性。
11. 【推薦】爲了達到代碼自解釋的目標,任何自定義編程元素在命名時,使用盡可能完整的單詞組合來表達其意。
正例:在 JDK 中,表達原子更新的類名爲:AtomicReferenceFieldUpdater。
反例:變量 int a 的隨意命名方式。
12. 【推薦】若是模塊、接口、類、方法使用了設計模式,在命名時需體現出具體模式。
說明:將設計模式體如今名字中,有利於閱讀者快速理解架構設計理念。
正例:public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;
13. 【推薦】接口類中的方法和屬性不要加任何修飾符號(public 也不要加),保持代碼的簡潔性,並加上有效的 Javadoc 註釋。儘可能不要在接口裏定義變量,若是必定要定義變量,確定是與接口方法相關,而且是整個應用的基礎常量。
正例:接口方法簽名 void commit();
接口基礎常量 String COMPANY = "alibaba";
反例:接口方法定義 public abstract void f();
說明:JDK8 中接口容許有默認實現,那麼這個 default 方法,是對全部實現類都有價值的默
認實現。
14. 接口和實現類的命名有兩套規則:
(1) 【強制】對於 Service 和 DAO 類,基於 SOA 的理念,暴露出來的服務必定是接口,內部的實現類用 Impl 的後綴與接口區別。
正例:CacheServiceImpl 實現 CacheService 接口。
(2) 【推薦】若是是形容能力的接口名稱,取對應的形容詞爲接口名(一般是–able 的形式)。
正例:AbstractTranslator 實現 Translatable 接口。
15. 【參考】枚舉類名建議帶上 Enum 後綴,枚舉成員名稱須要全大寫,單詞間用下劃線隔開。
說明:枚舉其實就是特殊的類,域成員均爲常量,且構造方法被默認強制是私有。
正例:枚舉名字爲 ProcessStatusEnum 的成員名稱:SUCCESS / UNKNOWN_REASON。
16. 【參考】各層命名
(1) Service/DAO 層方法命名規約
1)獲取單個對象的方法用 get 作前綴。
2)獲取多個對象的方法用 list 作前綴,複數形式結尾如:listObjects。
3)獲取統計值的方法用 count 作前綴。
4)插入的方法用 save/insert 作前綴。
5)刪除的方法用 remove/delete 作前綴。
6)修改的方法用 update 作前綴。
(2) 領域模型命名規約
1)數據對象:xxxDO,xxx 即爲數據表名。
2)數據傳輸對象:xxxDTO,xxx 爲業務領域相關的名稱。
3)展現對象:xxxVO,xxx 通常爲網頁名稱。
4)POJO 是 DO/DTO/BO/VO 的統稱,禁止命名成 xxxPOJO。
(二)常量定義
1.【強制】不容許任何魔法值(即未經預先定義的常量)直接出如今代碼中。
反例:String key = "Id#taobao_" + tradeId;
cache.put(key, value);
2.【強制】在 long 或者 Long 賦值時,數值後使用大寫的 L,不能是小寫的 l,小寫容易跟數字1 混淆,形成誤解。
說明:Long a = 2l; 寫的是數字的 21,仍是 Long 型的 2?
3.【推薦】不要使用一個常量類維護全部常量,要按常量功能進行歸類,分開維護。
說明:大而全的常量類,雜亂無章,使用查找功能才能定位到修改的常量,不利於理解和維護。
正例:緩存相關常量放在類 CacheConsts 下;系統配置相關常量放在類 ConfigConsts 下。
4.【推薦】 常量的複用層次有五層:跨應用共享常量、應用內共享常量、子工程內共享常量、包內共享常量、類內共享常量。
(1) 跨應用共享常量:放置在二方庫中,一般是 client.jar 中的 constant 目錄下。
(2) 應用內共享常量:放置在一方庫中,一般是子模塊中的 constant 目錄下。
反例:易懂變量也要統必定義成應用內共享常量,兩位攻城師在兩個類中分別定義了表示「是」的變量:
類 A 中:public static final String YES = "yes"; 3/38
類 B 中:public static final String YES = "y"; A.YES.equals(B.YES),預期是 true,但實際返回爲 false,致使線上問題。
(3)工程內部共享常量:即在當前子工程的 constant 目錄下。
(4)內共享常量:即在當前包下單獨的 constant 目錄下。
(5)內共享常量:直接在類內部 private static final 定義。
5.【推薦】若是變量值僅在一個固定範圍內變化用 enum 類型來定義。
說明:若是存在名稱以外的延伸屬性應使用 enum 類型,下面正例中的數字就是延伸信息,表示一年中的第幾個季節。
正例:
public enum SeasonEnum {
SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
private int seq;
SeasonEnum(int seq){
this.seq = seq;
}
}
(三)代碼格式
1.【強制】大括號的使用約定。若是是大括號內爲空,則簡潔地寫成{}便可,不須要換行;若是是非空代碼塊則:
1)左大括號前不換行。
2)左大括號後換行。
3)右大括號前換行。
4)右大括號後還有 else 等代碼則不換行;表示終止的右大括號後必須換行。
2.【強制】左小括號和字符之間不出現空格;一樣,右小括號和字符之間也不出現空格;而左大括號前須要空格。詳見第 5 條下方正例提示。
反例:if (空格 a == b 空格)
3.【強制】if/for/while/switch/do 等保留字與括號之間都必須加空格。
4.【強制】任何二目、三目運算符的左右兩邊都須要加一個空格。
說明:運算符包括賦值運算符=、邏輯運算符&&、加減乘除符號等。
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");
// 在右大括號後直接結束,則必須換行
}
}
6.【強制】註釋的雙斜線與註釋內容之間有且僅有一個空格。
正例:
// 這是示例註釋,請注意在雙斜線以後有一個空格
String ygb = new String();
7.【強制】單行字符數限制不超過 120 個,超出須要換行,換行時遵循以下原則:
1)第二行相對第一行縮進 4 個空格,從第三行開始,再也不繼續縮進,參考示例。
2)運算符與下文一塊兒換行。
3)方法調用的點符號與下文一塊兒換行。
4)方法調用中的多個參數須要換行時,在逗號後進行。 5) 在括號前不要換行,見反例。
正例: StringBuffer sb = new StringBuffer(); // 超過 120 個字符的狀況下,換行縮進 4 個空格,點號和方法名稱一塊兒換行 sb.append("zi").append("xin")... .append("huang")... .append("huang")... .append("huang"); 反例: StringBuffer sb = new StringBuffer(); // 超過 120 個字符的狀況下,不要在括號前換行 sb.append("zi").append("xin")...append ("huang"); // 參數不少的方法調用可能超過 120 個字符,不要在逗號前換行 method(args1, args2, args3, ... , argsX);
8.【強制】方法參數在定義和傳入時,多個參數逗號後邊必須加空格。
正例:下例中實參的 args1,後邊必需要有一個空格。
method(args1, args2, args3);
9.【強制】IDE 的 text file encoding 設置爲 UTF-8; IDE 中文件的換行符使用 Unix 格式,不要使用 Windows 格式。
10.【推薦】單個方法的總行數不超過 80 行。
說明:包括方法簽名、結束右大括號、方法內代碼、註釋、空行、回車及任何不可見字符的總行數不超過 80 行。
正例:代碼邏輯分清紅花和綠葉,個性和共性,綠葉邏輯單獨出來成爲額外方法,使主幹代碼更加清晰;共性邏輯抽取成爲共性方法,便於複用和維護。
11.【推薦】沒有必要增長若干空格來使某一行的字符與上一行對應位置的字符對齊。
正例:
int one = 1;
long two = 2L;
float three = 3F;
StringBuffer sb = new StringBuffer();
說明:增長 sb 這個變量,若是須要對齊,則給 a、b、c 都要增長几個空格,在變量比較多的狀況下,是很是累贅的事情。
12.【推薦】不一樣邏輯、不一樣語義、不一樣業務的代碼之間插入一個空行分隔開來以提高可讀性。
說明:任何情形,沒有必要插入多個空行進行隔開。
(四)OPP規約
1.【強制】避免經過一個類的對象引用訪問此類的靜態變量或靜態方法,無謂增長編譯器解析成本,直接用類名來訪問便可。
2.【強制】全部的覆寫方法,必須加@Override 註解。
說明:getObject()與 get0bject()的問題。一個是字母的 O,一個是數字的 0,加@Override能夠準確判斷是否覆蓋成功。另外,若是在抽象類中對方法簽名進行修改,其實現類會立刻編譯報錯。
3.【強制】相同參數類型,相同業務含義,才能夠使用 Java 的可變參數,避免使用 Object。
說明:可變參數必須放置在參數列表的最後。(提倡同窗們儘可能不用可變參數編程)
正例:public List<User> 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.關於基本數據類型與包裝數據類型的使用標準以下:
1)【強制】全部的 POJO 類屬性必須使用包裝數據類型。
2)【強制】RPC 方法的返回值和參數必須使用包裝數據類型。
3)【推薦】全部的局部變量使用基本數據類型。
說明:POJO 類屬性沒有初值是提醒使用者在須要使用時,必須本身顯式地進行賦值,任何NPE 問題,或者入庫檢查,都由使用者來保證。
正例:數據庫的查詢結果多是 null,由於自動拆箱,用基本數據類型接收有 NPE 風險。
反例:好比顯示成交總額漲跌狀況,即正負 x%,x 爲基本數據類型,調用的 RPC 服務,調用不成功時,返回的是默認值,頁面顯示爲 0%,這是不合理的,應該顯示成中劃線。因此包裝數據類型的 null 值,可以表示額外的信息,如:遠程調用失敗,異常退出。
9.【強制】定義 DO/DTO/VO 等 POJO 類時,不要設定任何屬性默認值。
反例:POJO 類的 gmtCreate 默認值爲 new Date(),可是這個屬性在數據提取時並無置入具體值,在更新其它字段時又附帶更新了此字段,致使建立時間被修改爲當前時間。
10.【強制】序列化類新增屬性時,請不要修改 serialVersionUID 字段,避免反序列失敗;若是徹底不兼容升級,避免反序列化混亂,那麼請修改 serialVersionUID 值。
說明:注意 serialVersionUID 不一致會拋出序列化運行時異常。
11.【強制】構造方法裏面禁止加入任何業務邏輯,若是有初始化邏輯,請放在 init 方法中。
12.【強制】POJO 類必須寫 toString 方法。使用 IDE 中的工具:source> generate toString時,若是繼承了另外一個 POJO 類,注意在前面加一下 super.toString。
說明:在方法執行拋出異常時,能夠直接調用 POJO 的 toString()方法打印其屬性值,便於排查問題。
13.【強制】禁止在 POJO 類中,同時存在對應屬性 xxx 的 isXxx()和 getXxx()方法。
說明:框架在調用屬性 xxx 的提取方法時,並不能肯定哪一個方法必定是被優先調用到。
14.【推薦】使用索引訪問用 String 的 split 方法獲得的數組時,需作最後一個分隔符後有無內容的檢查,不然會有拋 IndexOutOfBoundsException 的風險。
說明:
String str = "a,b,c,,";
String[] ary = str.split(",");
// 預期大於 3,結果是 3
System.out.println(ary.length);
15.【推薦】當一個類有多個構造方法,或者多個同名方法,這些方法應該按順序放置在一塊兒,
便於閱讀,此條規則優先於第 16 條規則。
16.【推薦】 類內方法定義的順序依次是:公有方法或保護方法 > 私有方法 > getter/setter方法。
說明:公有方法是類的調用者和維護者最關心的方法,首屏展現最好;保護方法雖然只是子類關心,也多是「模板設計模式」下的核心方法;而私有方法外部通常不須要特別關心,是一個黑盒實現;由於承載的信息價值較低,全部 Service 和 DAO 的 getter/setter 方法放在類體最後。
17.【推薦】setter 方法中,參數名稱與類成員變量名稱一致,this.成員名 = 參數名。在getter/setter 方法中,不要增長業務邏輯,增長排查問題的難度。
反例:
public Integer getData() {
if (condition) {
return this.data + 100;
} else {
return this.data - 100;
}
}
18.【推薦】循環體內,字符串的鏈接方式,使用 StringBuilder 的 append 方法進行擴展。
說明:下例中,反編譯出的字節碼文件顯示每次循環都會 new 出一個 StringBuilder 對象,而後進行 append 操做,最後經過 toString 方法返回 String 對象,形成內存資源浪費。
反例:
String str = "start";
for (int i = 0; i < 100; i++) {
str = str + "hello";
}
19.【推薦】final 能夠聲明類、成員變量、方法、以及本地變量,下列狀況使用 final 關鍵字:
1)不容許被繼承的類,如:String 類。
2)不容許修改引用的域對象。
3)不容許被重寫的方法,如:POJO 類的 setter 方法。
4)不容許運行過程當中從新賦值的局部變量。
5)避免上下文重複使用一個變量,使用 final 描述能夠強制從新定義一個變量,方便更好地進行重構。
20.【推薦】慎用 Object 的 clone 方法來拷貝對象。
說明:對象的 clone 方法默認是淺拷貝,若想實現深拷貝須要重寫 clone 方法實現域對象的深度遍歷式拷貝。
21.【推薦】類成員與方法訪問控制從嚴:
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 成員方法或成員變量,刪除一下,不得手心冒點汗嗎?變量像本身的小孩,儘可能在本身的視線內,變量做用域太大,無限制的處處跑,那麼你會擔憂的。
(五)集合處理
1.【強制】關於 hashCode 和 equals 的處理,遵循以下規則:
由於 Set 存儲的是不重複的對象,依據 hashCode 和 equals 進行判斷,因此 Set 存儲的
對象必須重寫這兩個方法。
說明:String 重寫了 hashCode 和 equals 方法,因此咱們能夠很是愉快地使用 String 對象做爲 key 來使用。
2.【強制】ArrayList的subList結果不可強轉成ArrayList,不然會拋出ClassCastException異常,即 java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。
說明:subList 返回的是 ArrayList 的內部類 SubList,並非 ArrayList 而是 ArrayList的一個視圖,對於 SubList 子列表的全部操做最終會反映到原列表上。
3.【強制】在 subList 場景中,高度注意對原集合元素的增長或刪除,均會致使子列表的遍歷、增長、刪除產生 ConcurrentModificationException 異常。
4.【強制】使用集合轉數組的方法,必須使用集合的 toArray(T[] array),傳入的是類型徹底同樣的數組,大小就是 list.size()。
說明:使用 toArray 帶參方法,入參分配的數組空間不夠大時,toArray 方法內部將從新分配內存空間,並返回新數組地址;若是數組元素個數大於實際所需,下標爲[ list.size() ]的數組元素將被置爲 null,其它數組元素保持原值,所以最好將方法入參數組大小定義與集合元素個數一致。
正例:
List<String> list = new ArrayList<String>(2);
list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);
反例:直接使用 toArray 無參方法存在問題,此方法返回值只能是 Object[]類,若強轉其它類型數組將出現 ClassCastException 錯誤。
5.【強制】使用工具類 Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方法,它的 add/remove/clear 方法會拋出 UnsupportedOperationException 異常。
說明:asList 的返回對象是一個 Arrays 內部類,並無實現集合的修改方法。Arrays.asList體現的是適配器模式,只是轉換接口,後臺的數據還是數組。
String[] str = new String[] { "you", "wu" };
List list = Arrays.asList(str);
第一種狀況:list.add("yangguanbao"); 運行時異常。
第二種狀況:str[0] = "gujin"; 那麼 list.get(0)也會隨之修改。
6.【強制】泛型通配符<? extends T>來接收返回的數據,此寫法的泛型集合不能使用 add 方 法,而<? super T>不能使用 get 方法,做爲接口調用賦值時易出錯。
說明:擴展說一下 PECS(Producer Extends Consumer Super)原則:第1、頻繁往外讀取內容的,適合用<? extends T>。第2、常常往裏插入的,適合用<? super T>。
7.【強制】不要在 foreach 循環裏進行元素的 remove/add 操做。remove 元素請使用 Iterator方式,若是併發操做,須要對 Iterator 對象加鎖。
正例: List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if (刪除元素的條件) { iterator.remove(); } } 反例: for (String item : list) { if ("1".equals(item)) { list.remove(item); } }
說明:以上代碼的執行結果確定會出乎你們的意料,那麼試一下把「1」換成「2」,會是一樣的結果嗎?
8.【強制】在 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<Student>() { @Override public int compare(Student o1, Student o2) { return o1.getId() > o2.getId() ? 1 : -1; } };
9.【推薦】集合泛型定義時,在 JDK7 及以上,使用 diamond 語法或全省略。
說明:菱形泛型,即 diamond,直接使用<>來指代前邊已經指定的類型。
正例: // <> diamond 方式 HashMap<String, String> userCache = new HashMap<>(16); // 全省略方式 ArrayList<User> users = new ArrayList(10);
10.【推薦】集合初始化時,指定集合初始值大小。
說明:HashMap 使用 HashMap(int initialCapacity) 初始化。
正例:initialCapacity = (須要存儲的元素個數 / 負載因子) + 1。注意負載因子(即 loader factor)默認爲 0.75,若是暫時沒法肯定初始值大小,請設置爲 16(即默認值)。
反例:HashMap 須要放置 1024 個元素,因爲沒有設置容量初始大小,隨着元素不斷增長,容量 7 次被迫擴大,resize 須要重建 hash 表,嚴重影響性能。
11.【推薦】使用 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 值組合集合。
12.【推薦】高度注意 Map 類集合 K/V 能不能存儲 null 值的狀況,以下表格:
反例: 因爲 HashMap 的干擾,不少人認爲 ConcurrentHashMap 是能夠置入 null 值,而事實上,存儲 null 值時會拋出 NPE 異常。
13.【參考】合理利用好集合的有序性(sort)和穩定性(order),避免集合的無序性(unsort)和不穩定性(unorder)帶來的負面影響。
說明:有序性是指遍歷的結果是按某種比較規則依次排列的。穩定性指集合每次遍歷的元素次序是必定的。如:ArrayList 是 order/unsort;HashMap 是 unorder/unsort;TreeSet 是order/sort。
14.【參考】利用 Set 元素惟一的特性,能夠快速對一個集合進行去重操做,避免使用 List 的contains 方法進行遍歷、對比、去重操做。
(六)併發處理
1.【強制】獲取單例對象須要保證線程安全,其中的方法也要保證線程安全。
說明:資源驅動類、工具類、單例工廠類都須要注意。
2.【強制】建立線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。
正例: public class TimerTaskThread extends Thread { public TimerTaskThread() { super.setName("TimerTaskThread"); ... } }
3.【強制】線程資源必須經過線程池提供,不容許在應用中自行顯式建立線程。
說明:使用線程池的好處是減小在建立和銷燬線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題。若是不使用線程池,有可能形成系統建立大量同類線程而致使消耗完內存或者「過分切換」的問題。
4.【強制】線程池不容許使用 Executors 去建立,而是經過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同窗更加明確線程池的運行規則,規避資源耗盡的風險。
說明:Executors 返回的線程池對象的弊端以下:
1)FixedThreadPool 和 SingleThreadPool:容許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而致使 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:容許的建立線程數量爲 Integer.MAX_VALUE,可能會建立大量的線程,從而致使 OOM。
5.【強制】SimpleDateFormat 是線程不安全的類,通常不要定義爲 static 變量,若是定義爲static,必須加鎖,或者使用 DateUtils 工具類。
正例:注意線程安全,使用 DateUtils。亦推薦以下處理: private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } };
說明:若是是 JDK8 的應用,能夠使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat,官方給出的解釋:simple beautiful strongimmutable thread-safe。
6.【強制】高併發時,同步調用應該去考量鎖的性能損耗。能用無鎖數據結構,就不要用鎖;能鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。
說明:儘量使加鎖的代碼塊工做量儘量的小,避免在鎖代碼塊中調用 RPC 方法。
7.【強制】對多個資源、數據庫表、對象同時加鎖時,須要保持一致的加鎖順序,不然可能會形成死鎖。
說明:線程一須要對錶 A、B、C 依次所有加鎖後才能夠進行更新操做,那麼線程二的加鎖順序也必須是 A、B、C,不然可能出現死鎖。
8.【強制】併發修改同一記錄時,避免更新丟失,須要加鎖。要麼在應用層加鎖,要麼在緩存加鎖,要麼在數據庫層使用樂觀鎖,使用 version 做爲更新依據。
說明:若是每次訪問衝突機率小於 20%,推薦使用樂觀鎖,不然使用悲觀鎖。樂觀鎖的重試次數不得小於 3 次。
9.【強制】多線程並行處理定時任務時,Timer 運行多個 TimeTask 時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,使用 ScheduledExecutorService 則沒有這個問題。
10.【推薦】使用 CountDownLatch 進行異步轉同步操做,每一個線程退出前必須調用 countDown方法,線程執行代碼注意 catch 異常,確保 countDown 方法被執行到,避免主線程沒法執行至 await 方法,直到超時才返回結果。
說明:注意,子線程拋出異常堆棧,不能在主線程 try-catch 到。
11.【推薦】避免 Random 實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一seed 致使的性能降低。
說明:Random 實例包括 java.util.Random 的實例或者 Math.random()的方式。
正例:在 JDK7 以後,能夠直接使用 API ThreadLocalRandom,而在 JDK7 以前,須要編碼保證每一個線程持有一個實例。
12.【推薦】在併發場景下,經過雙重檢查鎖(double-checked locking)實現延遲初始化的優化問題隱患(可參考 The "Double-Checked Locking is Broken" Declaration),推薦解決方案中較爲簡單一種(適用於 JDK5 及以上版本),將目標屬性聲明爲 volatile 型。
反例: class LazyInitDemo { private Helper helper = null; public Helper getHelper() { if (helper == null) synchronized(this) { if (helper == null) helper = new Helper(); } return helper; } // other methods and fields... }
13.【參考】volatile 解決多線程內存不可見問題。對於一寫多讀,是能夠解決變量同步問題,可是若是多寫,一樣沒法解決線程安全問題。若是是 count++操做,使用以下類實現:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 若是是 JDK8,推薦使用 LongAdder 對象,比 AtomicLong 性能更好(減小樂觀鎖的重試次數)。
14.【參考】 HashMap 在容量不夠進行 resize 時因爲高併發可能出現死鏈,致使 CPU 飆升,在開發過程當中能夠使用其它數據結構或加鎖來規避此風險。
15.【參考】ThreadLocal 沒法解決共享對象的更新問題,ThreadLocal 對象建議使用 static修飾。這個變量是針對一個線程內全部操做共享的,因此設置爲靜態變量,全部此類實例共享此靜態變量 ,也就是說在類第一次被使用時裝載,只分配一塊存儲空間,全部此類的對象(只要是這個線程內定義的)均可以操控這個變量。
(七)控制語句
1.【強制】在一個 switch 塊內,每一個 case 要麼經過 break/return 等來終止,要麼註釋說明程序將繼續執行到哪個 case 爲止;在一個 switch 塊內,都必須包含一個 default 語句而且放在最後,即便空代碼。
2.【強制】在 if/else/for/while/do 語句中必須使用大括號。即便只有一行代碼,避免採用單行的編碼方式:if (condition) statements;
3.【強制】在高併發場景中,避免使用」等於」判斷做爲中斷或退出的條件。
說明:若是併發控制沒有處理好,容易產生等值判斷被「擊穿」的狀況,使用大於或小於的區間判斷條件來代替。
反例:判斷剩餘獎品數量等於 0 時,終止發放獎品,但由於併發處理錯誤致使獎品數量瞬間變成了負數,這樣的話,活動沒法終止。
4.【推薦】表達異常的分支時,少用 if-else 方式,這種方式能夠改寫成:
if (condition) { ... return obj; } // 接着寫 else 的業務邏輯代碼;
說明:若是非得使用 if()...else if()...else...方式表達邏輯,【強制】避免後續代碼維護困難,請勿超過 3 層。
正例:超過 3 層的 if-else 的邏輯判斷代碼能夠使用衛語句、策略模式、狀態模式等來實現,
其中僞語句示例以下:
public void today() { if (isBusy()) { System.out.println(「change time.」); return; } if (isFree()) { System.out.println(「go to travel.」); return; } System.out.println(「stay at home to learn Alibaba Java Coding Guidelines.」); return; }
5.【推薦】除經常使用方法(如 getXxx/isXxx)等外,不要在條件判斷中執行其它複雜的語句,將複雜邏輯判斷的結果賦值給一個有意義的布爾變量名,以提升可讀性。
說明:不少 if 語句內的邏輯至關複雜,閱讀者須要分析條件表達式的最終結果,才能明確什麼樣的條件執行什麼樣的語句,那麼,若是閱讀者分析邏輯表達式錯誤呢?
正例: // 僞代碼以下 final boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed) { ... } 反例: if ((file.open(fileName, "w") != null) && (...) || (...)) { ... }
6.【推薦】循環體中的語句要考量性能,如下操做盡可能移至循環體外處理,如定義對象、變量、獲取數據庫鏈接,進行沒必要要的 try-catch 操做(這個 try-catch 是否能夠移至循環體外)。
7.【推薦】避免採用取反邏輯運算符。
說明:取反邏輯不利於快速理解,而且取反邏輯寫法必然存在對應的正向邏輯寫法。
正例:使用 if (x < 628) 來表達 x 小於 628。
反例:使用 if (!(x >= 628)) 來表達 x 小於 628。
8.【推薦】接口入參保護,這種場景常見的是用做批量操做的接口。
9.【參考】下列情形,須要進行參數校驗:
1)調用頻次低的方法。
2)執行時間開銷很大的方法。此情形中,參數校驗時間幾乎能夠忽略不計,但若是由於參數錯誤致使中間執行回退,或者錯誤,那得不償失。
3)須要極高穩定性和可用性的方法。
4)對外提供的開放接口,無論是 RPC/API/HTTP 接口。
5)敏感權限入口。
10.【參考】下列情形,不須要進行參數校驗:
1)極有可能被循環調用的方法。但在方法說明裏必須註明外部參數檢查要求。
2)底層調用頻度比較高的方法。畢竟是像純淨水過濾的最後一道,參數錯誤不太可能到底層纔會暴露問題。通常 DAO 層與 Service 層都在同一個應用中,部署在同一臺服務器中,因此 DAO 的參數校驗,能夠省略。
3)被聲明成 private 只會被本身代碼所調用的方法,若是可以肯定調用方法的代碼傳入參數已經作過檢查或者確定不會有問題,此時能夠不校驗參數。
(八)註釋規約
1. 【強制】類、類屬性、類方法的註釋必須使用 Javadoc 規範,使用/**內容*/格式,不得使用// xxx 方式。
說明:在 IDE 編輯窗口中,Javadoc 方式會提示相關注釋,生成 Javadoc 能夠正確輸出相應註釋;在 IDE 中,工程調用方法時,不進入方法便可懸浮提示方法、參數、返回值的意義,提升閱讀效率。
2.【強制】全部的抽象方法(包括接口中的方法)必需要用 Javadoc 註釋、除了返回值、參數、異常說明外,還必須指出該方法作什麼事情,實現什麼功能。
說明:對子類的實現要求,或者調用注意事項,請一併說明。
3.【強制】全部的類都必須添加建立者和建立日期。
4.【強制】方法內部單行註釋,在被註釋語句上方另起一行,使用//註釋。方法內部多行註釋使用/* */註釋,注意與代碼對齊。
5.【強制】全部的枚舉類型字段必需要有註釋,說明每一個數據項的用途。
6.【推薦】與其「半吊子」英文來註釋,不如用中文註釋把問題說清楚。專有名詞與關鍵字保持英文原文便可。
反例:「TCP 鏈接超時」解釋成「傳輸控制協議鏈接超時」,理解反而費腦筋。
7.【推薦】代碼修改的同時,註釋也要進行相應的修改,尤爲是參數、返回值、異常、核心邏輯等的修改。
說明:代碼與註釋更新不一樣步,就像路網與導航軟件更新不一樣步同樣,若是導航軟件嚴重滯後,就失去了導航的意義。
8.【參考】謹慎註釋掉代碼。在上方詳細說明,而不是簡單地註釋掉。若是無用,則刪除。
說明:代碼被註釋掉有兩種可能性:1)後續會恢復此段代碼邏輯。2)永久不用。前者若是沒有備註信息,難以知曉註釋動機。後者建議直接刪掉(代碼倉庫保存了歷史代碼)。
9.【參考】對於註釋的要求:第1、可以準確反應設計思想和代碼邏輯;第2、可以描述業務含義,使別的程序員可以迅速瞭解到代碼背後的信息。徹底沒有註釋的大段代碼對於閱讀者形同天書,註釋是給本身看的,即便隔很長時間,也能清晰理解當時的思路;註釋也是給繼任者看的,使其可以快速接替本身的工做。
10.【參考】好的命名、代碼結構是自解釋的,註釋力求精簡準確、表達到位。避免出現註釋的一個極端:過多過濫的註釋,代碼的邏輯一旦修改,修改註釋是至關大的負擔。
反例:
// put elephant into fridge
put(elephant, fridge);
方法名 put,加上兩個有意義的變量名 elephant 和 fridge,已經說明了這是在幹什麼,語義清晰的代碼不須要額外的註釋。
11.【參考】特殊註釋標記,請註明標記人與標記時間。注意及時處理這些標記,經過標記掃描,常常清理此類標記。線上故障有時候就是來源於這些標記處的代碼。
1)待辦事宜(TODO):( 標記人,標記時間,[預計處理時間])表示須要實現,但目前還未實現的功能。這其實是一個 Javadoc 的標籤,目前的 Javadoc尚未實現,但已經被普遍使用。只能應用於類,接口和方法(由於它是一個 Javadoc 標籤)。
2)錯誤,不能工做(FIXME):(標記人,標記時間,[預計處理時間])在註釋中用 FIXME 標記某代碼是錯誤的,並且不能工做,須要及時糾正的狀況。
(九)其餘
1.【強制】在使用正則表達式時,利用好其預編譯功能,能夠有效加快正則匹配速度。
說明:不要在方法體內定義:Pattern pattern = Pattern.compile(「規則」);
2.【強制】velocity 調用 POJO 類的屬性時,建議直接使用屬性名取值便可,模板引擎會自動按規範調用 POJO 的 getXxx(),若是是 boolean 基本數據類型變量(boolean 命名不須要加 is前綴),會自動調用 isXxx()方法。
說明:注意若是是 Boolean 包裝類對象,優先調用 getXxx()的方法。
3.【強制】後臺輸送給頁面的變量必須加$!{var}——中間的感嘆號。
說明:若是 var 等於 null 或者不存在,那麼${var}會直接顯示在頁面上。
4.【強制】注意 Math.random() 這個方法返回是 double 類型,注意取值的範圍 0≤x<1(可以取到零值,注意除零異常),若是想獲取整數類型的隨機數,不要將 x 放大 10 的若干倍而後取整,直接使用 Random 對象的 nextInt 或者 nextLong 方法。
5.【強制】獲取當前毫秒數 System.currentTimeMillis(); 而不是 new Date().getTime();
說明:若是想獲取更加精確的納秒級時間值,使用 System.nanoTime()的方式。在 JDK8 中,針對統計時間等場景,推薦使用 Instant 類。
6.【推薦】不要在視圖模板中加入任何複雜的邏輯。
說明:根據 MVC 理論,視圖的職責是展現,不要搶模型和控制器的活。
7.【推薦】任何數據結構的構造或初始化,都應指定大小,避免數據結構無限增加吃光內存。
8.【推薦】及時清理再也不使用的代碼段或配置信息。
說明:對於垃圾代碼或過期配置,堅定清理乾淨,避免程序過分臃腫,代碼冗餘。
正例:對於暫時被註釋掉,後續可能恢復使用的代碼片段,在註釋代碼上方,統一規定使用三個斜槓(///)來講明註釋掉代碼的理由。
(一)異常處理
1.【強制】Java 類庫中定義的能夠經過預檢查方式規避的 RuntimeException 異常不該該經過catch 的方式來處理,好比:NullPointerException,IndexOutOfBoundsException 等等。
說明:沒法經過預檢查的異常除外,好比,在解析字符串形式的數字時,不得不經過 catchNumberFormatException 來實現。
正例:if (obj != null) {...}
反例:try { obj.method(); } catch (NullPointerException e) {…}
2.【強制】異常不要用來作流程控制,條件控制。
說明:異常設計的初衷是解決程序運行中的各類意外狀況,且異常的處理效率比條件判斷方式要低不少。
3.【強制】catch 時請分清穩定代碼和非穩定代碼,穩定代碼指的是不管如何不會出錯的代碼。對於非穩定代碼的 catch 儘量進行區分異常類型,再作對應的異常處理。
說明:對大段代碼進行 try-catch,使程序沒法根據不一樣的異常作出正確的應激反應,也不利於定位問題,這是一種不負責任的表現。
正例:用戶註冊的場景中,若是用戶輸入非法字符,或用戶名稱已存在,或用戶輸入密碼過於簡單,在程序上做出分門別類的判斷,並提示給用戶。
4.【強制】捕獲異常是爲了處理它,不要捕獲了卻什麼都不處理而拋棄之,若是不想處理它,請將該異常拋給它的調用者。最外層的業務使用者,必須處理異常,將其轉化爲用戶能夠理解的內容。
5.【強制】有 try 塊放到了事務代碼中,catch 異常後,若是須要回滾事務,必定要注意手動回滾事務。
6.【強制】finally 塊必須對資源對象、流對象進行關閉,有異常也要作 try-catch。
說明:若是 JDK7 及以上,能夠使用 try-with-resources 方式。
7.【強制】不要在 finally 塊中使用 return。
說明:finally 塊中的 return 返回後方法結束執行,不會再執行 try 塊中的 return 語句。
8.【強制】捕獲異常與拋異常,必須是徹底匹配,或者捕獲異常是拋異常的父類。
說明:若是預期對方拋的是繡球,實際接到的是鉛球,就會產生意外狀況。
9.【推薦】方法的返回值能夠爲 null,不強制返回空集合,或者空對象等,必須添加註釋充分說明什麼狀況下會返回 null 值。
說明:本手冊明確防止 NPE 是調用者的責任。即便被調用方法返回空集合或者空對象,對調用者來講,也並不是高枕無憂,必須考慮到遠程調用失敗、序列化失敗、運行時異常等場景返回null 的狀況。
10.【推薦】防止 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 問題。
11.【推薦】定義時區分 unchecked / checked 異常,避免直接拋出 new RuntimeException(),更不容許拋出 Exception 或者 Throwable,應使用有業務含義的自定義異常。推薦業界已定義過的自定義異常,如:DAOException / ServiceException 等。
12.【參考】對於公司外的 http/api 開放接口必須使用「錯誤碼」;而應用內部推薦異常拋出;跨應用間 RPC 調用優先考慮使用 Result 方式,封裝 isSuccess()方法、「錯誤碼」、「錯誤簡短信息」。
說明:關於 RPC 方法返回方式使用 Result 方式的理由:
1)使用拋異常返回方式,調用方若是沒有捕獲到就會產生運行時錯誤。
2)若是不加棧信息,只是 new 自定義異常,加入本身的理解的 error message,對於調用端解決問題的幫助不會太多。若是加了棧信息,在頻繁調用出錯的狀況下,數據序列化和傳輸的性能損耗也是問題。
13.【參考】避免出現重複的代碼(Don’t Repeat Yourself),即 DRY 原則。
說明:隨意複製和粘貼代碼,必然會致使代碼的重複,在之後須要修改時,須要修改全部的副本,容易遺漏。必要時抽取共性方法,或者抽象公共類,甚至是組件化。
正例:一個類中有多個 public 方法,都須要進行數行相同的參數校驗操做,這個時候請抽取:private boolean checkParam(DTO dto) {...}
(二)日誌規約
1.【強制】應用中不可直接使用日誌系統(Log4j、Logback)中的 API,而應依賴使用日誌框架SLF4J 中的 API,使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式統一。
import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(Abc.class);
2.【強制】日誌文件至少保存 15 天,由於有些異常具有以「周」爲頻次發生的特色。
3.【強制】應用中的擴展日誌(如打點、臨時監控、訪問日誌等)命名方式:
appName_logType_logName.log。
logType:日誌類型,如 stats/monitor/access 等;
logName:日誌描述。
這種命名的好處:經過文件名就可知道日誌文件屬於什麼應用,什麼類型,什麼目的,也有利於歸類查找。
正例:mppserver 應用中單獨監控時區轉換異常,如:mppserver_monitor_timeZoneConvert.log
說明:推薦對日誌進行分類,如將錯誤日誌和業務日誌分開存放,便於開發人員查看,也便於經過日誌對系統進行及時監控。
4.【強制】對 trace/debug/info 級別的日誌輸出,必須使用條件輸出形式或者使用佔位符的方式。
說明:logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);若是日誌級別是 warn,上述日誌不會打印,可是會執行字符串拼接操做,若是 symbol 是對象,會執行 toString()方法,浪費了系統資源,執行了上述操做,最終日誌卻沒有打印。
正例:(條件)建設採用以下方式
if (logger.isDebugEnabled()) {
logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
}
正例:(佔位符)
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
5.【強制】避免重複打印日誌,浪費磁盤空間,務必在 log4j.xml 中設置 additivity=false。
正例:<logger name="com.taobao.dubbo.config" additivity="false">
6.【強制】異常信息應該包括兩類信息:案發現場信息和異常堆棧信息。若是不處理,那麼經過關鍵字 throws 往上拋出。
正例:logger.error(各種參數或者對象 toString() + "_" + e.getMessage(), e);
7.【推薦】謹慎地記錄日誌。生產環境禁止輸出 debug 日誌;有選擇地輸出 info 日誌;若是使用 warn 來記錄剛上線時的業務行爲信息,必定要注意日誌輸出量的問題,避免把服務器磁盤撐爆,並記得及時刪除這些觀察日誌。
說明:大量地輸出無效日誌,不利於系統性能提高,也不利於快速定位錯誤點。記錄日誌時請
思考:這些日誌真的有人看嗎?看到這條日誌你能作什麼?能不能給問題排查帶來好處?
8.【推薦】能夠使用 warn 日誌級別來記錄用戶輸入參數錯誤的狀況,避免用戶投訴時,無所適從。如非必要,請不要在此場景打出 error 級別,避免頻繁報警。
說明:注意日誌輸出的級別,error 級別只記錄系統邏輯出錯、異常或者重要的錯誤信息。
9.【推薦】儘可能用英文來描述日誌錯誤信息,若是日誌中的錯誤信息用英文描述不清楚的話使用中文描述便可,不然容易產生歧義。國際化團隊或海外部署的服務器因爲字符集問題,【強制】使用全英文來註釋和描述日誌錯誤信息。
1.【強制】好的單元測試必須遵照 AIR 原則。
說明:單元測試在線上運行時,感受像空氣(AIR)同樣並不存在,但在測試質量的保障上,倒是很是關鍵的。好的單元測試宏觀上來講,具備自動化、獨立性、可重複執行的特色。A:
2.【強制】單元測試應該是全自動執行的,而且非交互式的。測試用例一般是被按期執行的,執行過程必須徹底自動化纔有意義。輸出結果須要人工檢查的測試不是一個好的單元測試。單元測試中不許使用 System.out 來進行人肉驗證,必須使用 assert 來驗證。
3.【強制】保持單元測試的獨立性。爲了保證單元測試穩定可靠且便於維護,單元測試用例之間決不能互相調用,也不能依賴執行的前後次序。
反例:method2 須要依賴 method1 的執行,將執行結果做爲 method2 的輸入。
4.【強制】單元測試是能夠重複執行的,不能受到外界環境的影響。
說明:單元測試一般會被放到持續集成中,每次有代碼 check in 時單元測試都會被執行。若是單測對外部環境(網絡、服務、中間件等)有依賴,容易致使持續集成機制的不可用。
正例:爲了避免受外界環境影響,要求設計代碼時就把 SUT 的依賴改爲注入,在測試時用 spring這樣的 DI 框架注入一個本地(內存)實現或者 Mock 實現。
5.【強制】對於單元測試,要保證測試粒度足夠小,有助於精肯定位問題。單測粒度至可能是類級別,通常是方法級別。
說明:只有測試粒度小才能在出錯時儘快定位到出錯位置。單測不負責檢查跨類或者跨系統的交互邏輯,那是集成測試的領域。
6.【強制】核心業務、核心應用、核心模塊的增量代碼確保單元測試經過。
說明:新增代碼及時補充單元測試,若是新增代碼影響了原有單元測試,請及時修正。
7.【強制】單元測試代碼必須寫在以下工程目錄:src/test/java,不容許寫在業務代碼目錄下。
說明:源碼構建時會跳過此目錄,而單元測試框架默認是掃描此目錄。
8.【推薦】單元測試的基本目標:語句覆蓋率達到 70%;核心模塊的語句覆蓋率和分支覆蓋率都要達到 100%
說明:在工程規約的應用分層中提到的 DAO 層,Manager 層,可重用度高的 Service,都應該進行單元測試。
9.【推薦】編寫單元測試代碼遵照 BCDE 原則,以保證被測試模塊的交付質量。
10.【推薦】對於數據庫相關的查詢,更新,刪除等操做,不能假設數據庫裏的數據是存在的,或者直接操做數據庫把數據插入進去,請使用程序插入或者導入數據的方式來準備數據。
反例:刪除某一行數據的單元測試,在數據庫中,先直接手動增長一行做爲刪除目標,可是這一行新增數據並不符合業務插入規則,致使測試結果異常。
11.【推薦】和數據庫相關的單元測試,能夠設定自動回滾機制,不給數據庫形成髒數據。或者對單元測試產生的數據有明確的先後綴標識。
正例:在 RDC 內部單元測試中,使用 RDC_UNIT_TEST_的前綴標識數據。
12.【推薦】對於不可測的代碼建議作必要的重構,使代碼變得可測,避免爲了達到測試要求而書寫不規範測試代碼。
13.【推薦】在設計評審階段,開發人員須要和測試人員一塊兒肯定單元測試範圍,單元測試最好覆蓋全部測試用例。
14.【推薦】單元測試做爲一種質量保障手段,不建議項目發佈後補充單元測試用例,建議在項目提測前完成單元測試。
15. 【參考】爲了更方便地進行單元測試,業務代碼應避免如下狀況:
說明:多層條件語句建議使用衛語句、策略模式、狀態模式等方式重構。
16.【參考】不要對單元測試存在以下誤解:
1.【強制】隸屬於用戶我的的頁面或者功能必須進行權限控制校驗。
說明:防止沒有作水平權限校驗就可隨意訪問、修改、刪除別人的數據,好比查看他人的私信內容、修改他人的訂單。
2.【強制】用戶敏感數據禁止直接展現,必須對展現數據進行脫敏。
說明:中國大陸我的手機號碼顯示爲:158****9119,隱藏中間 4 位,防止隱私泄露。
3.【強制】用戶輸入的 SQL 參數嚴格使用參數綁定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 訪問數據庫。
4.【強制】用戶請求傳入的任何參數必須作有效性驗證。
說明:Java 代碼用正則來驗證客戶端的輸入,有些正則寫法驗證普通用戶輸入沒有可是若是攻擊人員使用的是特殊構造的字符串來驗證,有可能致使死循環的結果。
5.【強制】禁止向 HTML 頁面輸出未經安全過濾或未正確轉義的用戶數據。
6.【強制】表單、AJAX 提交必須執行 CSRF 安全驗證。
說明:CSRF(Cross-site request forgery)跨站請求僞造是一類常見編程漏洞。對於存在CSRF 漏洞的應用/網站,攻擊者能夠事先構造好 URL,只要受害者用戶一訪問,後臺便在用戶不知情的狀況下對數據庫中用戶參數進行相應修改。
說明:如註冊時發送驗證碼到手機,若是沒有限制次數和頻率,那麼能夠利用此功能騷擾到其它用戶,並形成短信平臺資源浪費。
(一)建表規約
說明:任何字段若是爲非負數,必須是 unsigned。
注意:POJO 類中的任何布爾類型的變量,都不要加 is 前綴,因此,須要在<resultMap>設置從 is_xxx 到 Xxx 的映射關係。數據庫表示是與否的值,使用 tinyint 類型,堅持 is_xxx 的命名方式是爲了明確其取值含義與取值範圍。
正例:表達邏輯刪除的字段名 is_deleted,1 表示刪除,0 表示未刪除。
2.【強制】表名、字段名必須使用小寫字母或數字,禁止出現數字開頭,禁止兩個下劃線中間只出現數字。數據庫字段名的修改代價很大,由於沒法進行預發佈,因此字段名稱須要慎重考慮。
說明:MySQL 在 Windows 下不區分大小寫,但在 Linux 下默認是區分大小寫。所以,數據庫名、表名、字段名,都不容許出現任何大寫字母,避免節外生枝。
正例:aliyun_admin,rdc_config,level3_name
反例:AliyunAdmin,rdcConfig,level_3_name
說明:表名應該僅僅表示表裏面的實體內容,不該該表示實體數量,對應於 DO 類名也是單數形式,符合表達習慣。
4.【強制】禁用保留字,如 desc、range、match、delayed 等,請參考 MySQL 官方保留字。
說明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的簡稱。
說明:float 和 double 在存儲的時候,存在精度損失的問題,極可能在值的比較時,獲得不正確的結果。若是存儲的數據範圍超過 decimal 的範圍,建議將數據拆成整數和小數分開存儲。
7. 【強制】若是存儲的字符串長度幾乎相等,使用 char 定長字符串類型。8.【強制】varchar 是可變長字符串,不預先分配存儲空間,長度不要超過 5000,若是存儲長度大於此值,定義字段類型爲 text,獨立出來一張表,用主鍵來對應,避免影響其它字段索引效率。
說明:其中 id 必爲主鍵,類型爲 bigint unsigned、單表時自增、步長爲 1。gmt_create, gmt_modified 的類型均爲 datetime 類型,前者如今時表示主動建立,後者過去分詞表示被動更新。
正例:alipay_task / force_project / trade_config
11. 【推薦】庫名與應用名稱儘可能一致。12.【推薦】若是修改字段含義或對字段表示的狀態追加時,須要及時更新字段註釋。
1)不是頻繁修改的字段。
2)不是 varchar 超長字段,更不能是 text 字段。
正例:商品類目名稱使用頻率高,字段長度短,名稱基本一成不變,可在相關聯的表中冗餘存儲類目名稱,避免關聯查詢。
說明:若是預計三年後的數據量根本達不到這個級別,請不要在建立表時就分庫分表。
正例:以下表,其中無符號值能夠避免誤存負數,且擴大了表示範圍。
(二)索引規約
1.【強制】業務上具備惟一特性的字段,即便是多個字段的組合,也必須建成惟一索引。
說明:不要覺得惟一索引影響了 insert 速度,這個速度損耗能夠忽略,但提升查找速度是明顯的;另外,即便在應用層作了很是完善的校驗控制,只要沒有惟一索引,根據墨菲定律,必然有髒數據產生。
2.【強制】超過三個表禁止 join。須要 join 的字段,數據類型必須絕對一致;多表關聯查詢時,保證被關聯的字段須要有索引。
說明:即便雙表 join 也要注意表索引、SQL 性能。
3.【強制】在 varchar 字段上創建索引時,必須指定索引長度,不必對全字段創建索引,根據實際文本區分度決定索引長度便可。
說明:索引的長度與區分度是一對矛盾體,通常對字符串類型數據,長度爲 20 的索引,區分度會高達 90%以上,能夠使用 count(distinct left(列名, 索引長度))/count(*)的區分度來肯定。
4.【強制】頁面搜索嚴禁左模糊或者全模糊,若是須要請走搜索引擎來解決。
說明:索引文件具備 B-Tree 的最左前綴匹配特性,若是左邊的值未肯定,那麼沒法使用此索引。
5.【推薦】若是有 order by 的場景,請注意利用索引的有序性。order by 最後的字段是組合索引的一部分,而且放在索引組合順序的最後,避免出現 file_sort 的狀況,影響查詢性能。
正例:where a=? and b=? order by c; 索引:a_b_c
反例:索引中有範圍查找,那麼索引有序性沒法利用,如:WHERE a>10 ORDER BY b; 索引a_b 沒法排序。
6.【推薦】利用覆蓋索引來進行查詢操做,避免回表。
說明:若是一本書須要知道第 11 章是什麼標題,會翻開第 11 章對應的那一頁嗎?目錄瀏覽一下就好,這個目錄就是起到覆蓋索引的做用。
正例:可以創建索引的種類分爲主鍵索引、惟一索引、普通索引三種,而覆蓋索引只是一種查詢的一種效果,用 explain 的結果,extra 列會出現:using index。
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
說明:
1)consts 單表中最多隻有一個匹配行(主鍵或者惟一索引),在優化階段便可讀取到數據。
2)ref 指的是使用普通的索引(normal index)。 3)range 對索引進行範圍檢索。
反例:explain 表的結果,type=index,索引物理文件全掃描,速度很是慢,這個 index 級別比較 range 還低,與全表掃描是小巫見大巫。
正例:若是 where a=? and b=? ,若是 a 列的幾乎接近於惟一值,那麼只須要單建 idx_a索引便可。
說明:存在非等號和等號混合時,在建索引時,請把等號條件的列前置。如:where c>? and d=? 那麼即便 c 的區分度更高,也必須把 d 放在索引的最前列,即索引 idx_d_c。
11.【參考】建立索引時避免有以下極端誤解:
1)寧濫勿缺。認爲一個查詢就須要建一個索引。
2)寧缺勿濫。認爲索引會消耗空間、嚴重拖慢更新和新增速度。
3)抵制唯一索引。認爲業務的唯一性一概須要在應用層經過「先查後插」方式解決。
1.【強制】不要使用 count(列名)或 count(常量)來替代 count(*),count(*)是 SQL92 定義的標準統計行數的語法,跟數據庫無關,跟 NULL 和非 NULL 無關。
說明:count(*)會統計值爲 NULL 的行,而 count(列名)不會統計此列爲 NULL 值的行。
2.【強制】count(distinct col) 計算該列除 NULL 以外的不重複行數,注意 count(distinct col1, col2) 若是其中一列全爲 NULL,那麼即便另外一列有不一樣的值,也返回爲 0。
正例:能夠使用以下方式來避免 sum 的 NPE 問題:SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table;
4. 【強制】使用 ISNULL()來判斷是否爲 NULL 值。說明:NULL 與任何值的直接比較都爲 NULL。
1)NULL<>NULL 的返回結果是 NULL,而不是 false。
2)NULL=NULL 的返回結果是 NULL,而不是 true。
3)NULL<>1 的返回結果是 NULL,而不是 true。
5.【強制】在代碼中寫分頁查詢邏輯時,若 count 爲 0 應直接返回,避免執行後面的分頁語句。
說明:以學生和成績的關係爲例,學生表中的 student_id是主鍵,那麼成績表中的 student_id則爲外鍵。若是更新學生表中的 student_id,同時觸發成績表中的 student_id 更新,即爲級聯更新。外鍵與級聯更新適用於單機低併發,不適合分佈式、高併發集羣;級聯更新是強阻塞,存在數據庫更新風暴的風險;外鍵影響數據庫的插入速度。
7.【強制】禁止使用存儲過程,存儲過程難以調試和擴展,更沒有移植性。
9.【推薦】in 操做能避免則避免,若實在避免不了,須要仔細評估 in 後邊的集合元素數量,控制在 1000 個以內。
說明:
SELECT LENGTH("輕鬆工做"); 返回爲 12
SELECT CHARACTER_LENGTH("輕鬆工做"); 返回爲 4
若是須要存儲表情,那麼選擇 utf8mb4 來進行存儲,注意它與 utf-8 編碼的區別。
說明:TRUNCATE TABLE 在功能上與不帶 WHERE 子句的 DELETE 語句相同。
(四)ORM映射
說明:
1)增長查詢分析器解析成本。
2)增減字段容易與 resultMap 配置不一致。
3)無用字段增長網絡消耗,尤爲是 text 類型的字段。
說明:參見定義 POJO 類以及數據庫字段定義規定,在<resultMap>中增長映射,是必須的。在 MyBatis Generator 生成的代碼中,須要進行對應的修改。
說明:配置映射關係,使字段與 DO 類解耦,方便維護。
4.【強制】sql.xml 配置參數使用:#{},#param# 不要使用${} 此種方式容易出現 SQL 注入。
說明:其實現方式是在數據庫取到statementName對應的SQL語句的全部記錄,再經過subList取 start,size 的子集合。
正例:Map<String, Object> map = new HashMap<>();
map.put("start", start);
map.put("size", size);
6.【強制】不容許直接拿 HashMap 與 Hashtable 做爲查詢結果集的輸出。
7.【強制】更新數據表記錄時,必須同時更新記錄對應的 gmt_modified 字段值爲當前時間。
9.【參考】@Transactional 事務不要濫用。事務會影響數據庫的 QPS,另外使用事務的地方須要考慮各方面的回滾方案,包括緩存回滾、搜索引擎回滾、消息補償、統計修正等。
3.【參考】分層領域模型規約:
1) GroupID 格式:com.{公司/BU }.業務線 [.子業務線],最多 4 級。
說明:{公司/BU} 例如:alibaba/taobao/tmall/aliexpress 等 BU 一級;子業務線可選。
正例:com.taobao.jstorm 或 com.alibaba.dubbo.register
2) ArtifactID 格式:產品線名-模塊名。語義不重複不遺漏,先到中央倉庫去查證一下。
正例:dubbo-client / fastjson-api / jstorm-tool
3) Version:詳細規定參考下方。
1)主版本號:產品方向改變,或者大規模 API 不兼容,或者架構不兼容升級。
2)次版本號:保持相對兼容性,增長主要功能特性,影響範圍極小的 API 不兼容修改。
3)修訂號:保持徹底兼容性,修復 BUG、新增次要功能特性等。
說明:注意起始版本號必須爲:1.0.0,而不是 0.0.1 正式發佈的類庫必須先去中央倉庫進行查證,使版本號有延續性,正式版本號不容許覆蓋升級。如當前版本:1.3.3,那麼下一個合理的版本號:1.3.4 或 1.4.0 或 2.0.0
說明:不依賴 SNAPSHOT 版本是保證應用發佈的冪等性。另外,也能夠加快編譯時的打包構建。
5.【強制】二方庫裏能夠定義枚舉類型,參數能夠使用枚舉類型,可是接口返回值不容許使用枚舉類型或者包含枚舉類型的 POJO 對象。
說明:依賴 springframework-core,-context,-beans,它們都是同一個版本,能夠定義一個變量來保存版本:${spring.version},定義依賴的時候,引用該版本。
說明:在本地調試時會使用各子項目指定的版本號,可是合併成一個 war,只能有一個版本號出如今最後的 lib 目錄中。可能出現線下調試是正確的,發佈到線上卻出故障的問題。
說明:<dependencyManagement>裏只是聲明版本,並不實現引入,所以子項目須要顯式的聲明依賴,version 和 scope 都讀取自父 pom。而<dependencies>全部聲明在主 pom 的 <dependencies>裏的依賴都會自動引入,並默認被全部的子項目繼承。
9.【推薦】二方庫不要有配置項,最低限度不要再增長配置項。
1)精簡可控原則。移除一切沒必要要的 API 和依賴,只包含 Service API、必要的領域模型對象、Utils 類、常量、枚舉等。若是依賴其它二方庫,儘可能是 provided 引入,讓二方庫使用者去依賴具體版本號;無 log 具體實現,只依賴日誌框架。
2)穩定可追溯原則。每一個版本的變化應該被記錄,二方庫由誰維護,源碼在哪裏,都須要能方便查到。除非用戶主動升級版本,不然公共二方庫的行爲不該該發生變化。
1.【推薦】高併發服務器建議調小 TCP 協議的 time_wait 超時時間。
說明:操做系統默認 240 秒後,纔會關閉處於 time_wait 狀態的鏈接,在高併發訪問下,服務器端會由於處於 time_wait 的鏈接數太多,可能沒法創建新的鏈接,因此須要在服務器上調小此等待值。
正例:在 linux 服務器上請經過變動/etc/sysctl.conf 文件去修改該缺省值(秒):net.ipv4.tcp_fin_timeout = 30
2. 【推薦】調大服務器所支持的最大文件句柄數(File Descriptor,簡寫爲 fd)。說明:主流操做系統的設計是將 TCP/UDP 鏈接採用與文件同樣的方式去管理,即一個鏈接對應於一個 fd。主流的 linux 服務器默認所支持最大 fd 數量爲 1024,當併發鏈接數很大時很容易由於 fd 不足而出現「open too many files」錯誤,致使新的鏈接沒法創建。 建議將 linux服務器所支持的最大句柄數調高數倍(與服務器的內存數量相關)。
說明:OOM 的發生是有機率的,甚至相隔數月纔出現一例,出錯時的堆內信息對解決問題很是有幫助。
4.【推薦】在線上生產環境,JVM 的 Xms 和 Xmx 設置同樣大小的內存容量,避免在 GC 後調整堆大小帶來的壓力。
說明:有缺陷的底層數據結構容易致使系統風險上升,可擴展性降低,重構成本也會因歷史數據遷移和系統平滑過渡而陡然增長,因此,存儲方案和數據結構須要認真地進行設計和評審,生產環境提交執行後,須要進行 double check。
正例:評審內容包括存儲介質選型、表結構設計可否知足技術方案、存取性能和存儲空間可否知足業務發展、表或字段之間的辯證關係、字段名稱、字段類型、索引等;數據結構變動(如在原有表中新增字段)也須要進行評審經過後上線。
2.【強制】在需求分析階段,若是與系統交互的 User 超過一類而且相關的 User Case 超過 5 個,使用用例圖來表達更加清晰的結構化需求。
說明:狀態圖的核心是對象狀態,首先明確對象有多少種狀態,而後明確兩兩狀態之間是否存在直接轉換關係,再明確觸發狀態轉換的條件是什麼。
正例:淘寶訂單狀態有已下單、待付款、已付款、待發貨、已發貨、已收貨等。好比已下單與已收貨這兩種狀態之間是不可能有直接轉換關係的。
說明:時序圖反映了一系列對象間的交互與協做關係,清晰立體地反映系統的調用縱深鏈路。
說明:類圖像建築領域的施工圖,若是搭平房,可能不須要,但若是建造螞蟻 Z 空間大樓,確定須要詳細的施工圖。
說明:活動圖是流程圖的擴展,增長了可以體現協做關係的對象泳道,支持表示併發等。
反例:用戶在淘寶付款過程當中,銀行扣款成功,發送給用戶扣款成功短信,可是支付寶入款時因爲斷網演練產生異常,淘寶訂單頁面依然顯示未付款,致使用戶投訴。
8.【推薦】類在設計與實現時要符合單一原則。
說明:單一原則最易理解倒是最難實現的一條規則,隨着系統演進,不少時候,忘記了類設計的初衷。
9.【推薦】謹慎使用繼承的方式來進行擴展,優先使用聚合/組合的方式來實現。
說明:不得已使用繼承的話,必須符合里氏代換原則,此原則說父類可以出現的地方子類必定可以出現,好比,「把錢交出來」,錢的子類美圓、歐元、人民幣等均可以出現。
10.【推薦】系統設計時,根據依賴倒置原則,儘可能依賴抽象類與接口,有利於擴展與維護。
說明:低層次模塊依賴於高層次模塊的抽象,方便系統間的解耦。
說明:極端狀況下,交付的代碼都是不可修改的,同一業務域內的需求變化,經過模塊或類的擴展來實現。
說明:隨着代碼的重複次數不斷增長,維護成本指數級上升。
說明:敏捷開發是快速交付迭代可用的系統,省略多餘的設計方案,摒棄傳統的審批流程,但核心關鍵點上的必要設計和文檔沉澱是須要的。
反例:某團隊爲了業務快速發展,敏捷成了產品經理催進度的藉口,系統中均是勉強能運行但 像麪條同樣的代碼,可維護性和可擴展性極差,一年以後,不得不進行大規模重構,得不償失。
說明:避免爲了設計而設計,系統設計文檔有助於後期的系統維護,因此設計結果須要進行分類歸檔保存。
15.【參考】設計的本質就是識別和表達系統難點,找到系統的變化點,並隔離變化點。
說明:世間衆多設計模式目的是相同的,即隔離系統變化點。
16.【參考】系統架構設計的目的:
說明:大部分摘自阿里巴巴Java開發手冊