前言
一、程式設計規約......................................................................... 1
(一) 命名風格................................................................... 1
(二) 常量定義................................................................... 3
(三) 代碼格式................................................................... 4
(四) OOP 規約................................................................... 6
(五) 集合處理................................................................... 9
(六) 并發處理.................................................................. 12
(七) 控制語句.................................................................. 14
(八) 注釋規約.................................................................. 16
(九) 其它...................................................................... 18
二、異常日志........................................................................ 19
(一) 異常處理.................................................................. 19
(二) 日志規約.................................................................. 20
三、單元測試........................................................................ 22
四、安全規約........................................................................ 24
五、MySQL 資料庫.................................................................... 25
(一) 建表規約.................................................................. 25
(二) 索引規約.................................................................. 26
(三) SQL 語句.................................................................. 28
(四) ORM 映射.................................................................. 29
六、工程結構........................................................................ 31
(一) 應用分層.................................................................. 31
(二) 二方庫依賴................................................................ 32
(三) 伺服器.................................................................... 33
七、設計規約........................................................................ 35
附 1:版本曆史...................................................................... 37
附 2:專有名詞解釋.................................................................. 38
(注:浏覽時請使用 PDF 左側導航欄)
阿裡巴巴 Java 開發手冊
Java 開發手冊
一、程式設計規約
(一) 命名風格
1. 【強制】代碼中的命名均不能以下劃線或美元符号開始,也不能以下劃線或美元符号結束。反例:_name / __name / $name / name_ / name$ / name__
2. 【強制】代碼中的命名嚴禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。
說明:正确的英文拼寫和文法可以讓閱讀者易于了解,避免歧義。注意,即使純拼音命名方式也要避免采用。
正例:alibaba/ taobao / youku / hangzhou 等國際通用的名稱,可視同英文。
反例:DaZhePromotion[打折] /getPingfenByName() [評分] / int 某變量= 3
3. 【強制】類名使用 UpperCamelCase 風格,但以下情形例外:DO / BO / DTO / VO / AO /
PO / UID 等。
正例:MarcoPolo/ UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:macroPolo/ UserDo / XMLService / TCPUDPDeal / TAPromotion
4. 【強制】方法名、參數名、成員變量、局部變量都統一使用 lowerCamelCase 風格,必須遵從駝峰形式。
正例:localValue / getHttpMessage() /inputUserId
5. 【強制】常量命名全部大寫,單詞間用下劃線隔開,力求語義表達完整清楚,不要嫌名字長。正例:MAX_STOCK_COUNT
反例:MAX_COUNT
6. 【強制】抽象類命名使用 Abstract 或 Base 開頭;異常類命名使用 Exception 結尾;測試類命名以它要測試的類的名稱開始,以 Test 結尾。
7. 【強制】類型與中括号緊挨相連來表示數組。正例:定義整形數組 int[] arrayDemo;
反例:在 main 參數中,使用 String args[]來定義。
8. 【強制】POJO 類中布爾類型的變量,都不要加 is 字首,否則部分架構解析會引起序列化錯誤。反例:定義為基本資料類型 Boolean isDeleted 的屬性,它的方法也是 isDeleted(),RPC
1/38
阿裡巴巴 Java 開發手冊
架構在反向解析的時候,“誤以為”對應的屬性名稱是 deleted,導緻屬性擷取不到,進而抛出異常。
9. 【強制】包名統一使用小寫,點分隔符之間有且僅有一個自然語義的英語單詞。包名統一使用單數形式,但是類名如果有複數含義,類名可以使用複數形式。
正例:應用工具類包名為 com.alibaba.ai.util、類名為 MessageUtils(此規則參考 spring
的架構結構)
10. 【強制】杜絕完全不規範的縮寫,避免望文不知義。
反例:AbstractClass“縮寫”命名成 AbsClass;condition“縮寫”命名成condi,此類随
意縮寫嚴重降低了代碼的可閱讀性。
11. 【推薦】為了達到代碼自解釋的目标,任何自定義程式設計元素在命名時,使用盡量完整的單詞
組合來表達其意。
正例:在 JDK 中,表達原子更新的類名為:AtomicReferenceFieldUpdater。
反例:變量 int a 的随意命名方式。
12. 【推薦】如果子產品、接口、類、方法使用了設計模式,在命名時需展現出具體模式。
說明:将設計模式展現在名字中,有利于閱讀者快速了解架構設計理念。
正例:publicclass 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接口。
2/38
阿裡巴巴 Java 開發手冊
15. 【參考】枚舉類名建議帶上 Enum 字尾,枚舉成員名稱需要全大寫,單詞間用下劃線隔開。
說明:枚舉其實就是特殊的類,域成員均為常量,且構造方法被預設強制是私有。
正例:枚舉名字為 ProcessStatusEnum 的成員名稱:SUCCESS / UNKNOWN_REASON。
16. 【參考】各層命名規約:
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。
(二) 常量定義
1. 【強制】不允許任何魔法值(即未經預先定義的常量)直接出現在代碼中。反例:String key ="Id#taobao_"+ tradeId;
cache.put(key, value);
2. 【強制】在 long 或者 Long 指派時,數值後使用大寫的 L,不能是小寫的 l,小寫容易跟數字1 混淆,造成誤解。
說明:Longa = 2l; 寫的是數字的21,還是 Long 型的2?
3. 【推薦】不要使用一個常量類維護所有常量,要按常量功能進行歸類,分開維護。
說明:大而全的常量類,雜亂無章,使用查找功能才能定位到修改的常量,不利于了解和維護。
正例:緩存相關常量放在類 CacheConsts 下;系統配置相關常量放在類 ConfigConsts 下。
4. 【推薦】常量的複用層次有五層:跨應用共享常量、應用内共享常量、子工程内共享常量、包内共享常量、類内共享常量。
1)跨應用共享常量:放置在二方庫中,通常是client.jar中的constant目錄下。
2)應用内共享常量:放置在一方庫中,通常是子子產品中的constant目錄下。
反例:易懂變量也要統一定義成應用内共享常量,兩位攻城師在兩個類中分别定義了表示“是”的變量:
類A中:public staticfinal String YES = "yes";
3/38
阿裡巴巴 Java 開發手冊
類B中:public staticfinal String YES = "y";
A.YES.equals(B.YES),預期是 true,但實際傳回為 false,導緻線上問題。
3)子工程内部共享常量:即在目前子工程的constant目錄下。
4)包内共享常量:即在目前包下單獨的constant目錄下。
5)類内共享常量:直接在類内部privatestatic final定義。
5. 【推薦】如果變量值僅在一個固定範圍内變化用 enum 類型來定義。
說明:如果存在名稱之外的延伸屬性應使用 enum 類型,下面正例中的數字就是延伸資訊,表示一年中的第幾個季節。
正例:
public enum SeasonEnum{
SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
private int seq;
SeasonEnum(intseq){
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。
4/38
阿裡巴巴 Java 開發手冊
正例:(涉及 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 = newString();
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);
5/38
阿裡巴巴 Java 開發手冊
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.【推薦】不同邏輯、不同語義、不同業務的代碼之間插入一個空行分隔開來以提升可讀性。說明:任何情形,沒有必要插入多個空行進行隔開。
(四) OOP 規約
1. 【強制】避免通過一個類的對象引用通路此類的靜态變量或靜态方法,無謂增加編譯器解析成本,直接用類名來通路即可。
2. 【強制】所有的覆寫方法,必須加@Override 注解。
說明:getObject()與 get0bject()的問題。一個是字母的 O,一個是數字的0,加@Override
可以準确判斷是否覆寫成功。另外,如果在抽象類中對方法簽名進行修改,其實作類會馬上編譯報錯。
3. 【強制】相同參數類型,相同業務含義,才可以使用 Java 的可變參數,避免使用 Object。說明:可變參數必須放置在參數清單的最後。(提倡同學們盡量不用可變參數程式設計)
正例:publicList<User> listUsers(String type, Long... ids) {...}
4. 【強制】外部正在調用或者二方庫依賴的接口,不允許修改方法簽名,避免對接口調用方産生影響。接口過時必須加@Deprecated 注解,并清晰地說明采用的新接口或者新服務是什麼。
5. 【強制】不能使用過時的類或方法。
說明:java.net.URLDecoder中的方法decode(String encodeStr) 這個方法已經過時,應
6/38
阿裡巴巴 Java 開發手冊
該使用雙參數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()方法列印其屬性值,便于排查問題。
7/38
阿裡巴巴 Java 開發手冊
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方法。
8/38
阿裡巴巴 Java 開發手冊
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 的處理,遵循如下規則:
1) 隻要重寫equals,就必須重寫hashCode。
2) 因為Set存儲的是不重複的對象,依據hashCode和equals進行判斷,是以Set存儲的對象必須重寫這兩個方法。
3) 如果自定義對象作為Map的鍵,那麼必須重寫hashCode和equals。
說明: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 異常。
9/38
阿裡巴巴 Java 開發手冊
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. 【強制】泛型通配符<? extendsT>來接收傳回的資料,此寫法的泛型集合不能使用add方法,而<? super T>不能使用 get 方法,作為接口調用指派時易出錯。
說明:擴充說一下 PECS(Producer Extends Consumer Super)原則:第一、頻繁往外讀取内容的,适合用<? extendsT>。第二、經常往裡插入的,适合用<? 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();
}
}
10/38
阿裡巴巴 Java 開發手冊
反例:
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 = newHashMap<>(16);
// 全省略方式
ArrayList<User> users = new ArrayList(10);
10. 【推薦】集合初始化時,指定集合初始值大小。
說明:HashMap使用 HashMap(intinitialCapacity) 初始化。
正例:initialCapacity=(需要存儲的元素個數/負載因子) + 1。注意負載因子(即loaderfactor)預設為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 值組合集合。
11/38
阿裡巴巴 Java 開發手冊
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. 【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式建立線程。
說明:使用線程池的好處是減少在建立和銷毀線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統建立大量同類線程而導緻消耗完記憶體或者“過度切換”的問題。
12/38
阿裡巴巴 Java 開發手冊
4. 【強制】線程池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式,這樣
的處理方式讓寫的同學更加明确線程池的運作規則,規避資源耗盡的風險。
說明:Executors傳回的線程池對象的弊端如下:
1)FixedThreadPool和SingleThreadPool:
允許的請求隊列長度為Integer.MAX_VALUE,可能會堆積大量的請求,進而導緻 OOM。
2)CachedThreadPool和ScheduledThreadPool:
允許的建立線程數量為Integer.MAX_VALUE,可能會建立大量的線程,進而導緻 OOM。
5. 【強制】SimpleDateFormat 是線程不安全的類,一般不要定義為 static 變量,如果定義為
static,必須加鎖,或者使用 DateUtils 工具類。
正例:注意線程安全,使用 DateUtils。亦推薦如下處理:
privatestatic final ThreadLocal<DateFormat> df = newThreadLocal<DateFormat>() { @Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
說明:如果是 JDK8的應用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,
DateTimeFormatter 代替 SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable 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 到。
13/38
阿裡巴巴 Java 開發手冊
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;
14/38
阿裡巴巴 Java 開發手冊
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(“stayat 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) && (...)|| (...)) {
...
}
15/38
阿裡巴巴 Java 開發手冊
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. 【強制】所有的類都必須添加建立者和建立日期。
16/38
阿裡巴巴 Java 開發手冊
4. 【強制】方法内部單行注釋,在被注釋語句上方另起一行,使用//注釋。方法内部多行注釋使用注釋,注意與代碼對齊。
5. 【強制】所有的枚舉類型字段必須要有注釋,說明每個資料項的用途。
6. 【推薦】與其“半吊子”英文來注釋,不如用中文注釋把問題說清楚。專有名詞與關鍵字保持英文原文即可。
反例:“TCP連接配接逾時”解釋成“傳輸控制協定連接配接逾時”,了解反而費腦筋。
7. 【推薦】代碼修改的同時,注釋也要進行相應的修改,尤其是參數、傳回值、異常、核心邏輯等的修改。
說明:代碼與注釋更新不同步,就像路網與導航軟體更新不同步一樣,如果導航軟體嚴重滞後,就失去了導航的意義。
8. 【參考】謹慎注釋掉代碼。在上方詳細說明,而不是簡單地注釋掉。如果無用,則删除。
說明:代碼被注釋掉有兩種可能性:1)後續會恢複此段代碼邏輯。2)永久不用。前者如果沒有備注資訊,難以知曉注釋動機。後者建議直接删掉(代碼倉庫儲存了曆史代碼)。
9. 【參考】對于注釋的要求:第一、能夠準确反應設計思想和代碼邏輯;第二、能夠描述業務含義,使别的程式員能夠迅速了解到代碼背後的資訊。完全沒有注釋的大段代碼對于閱讀者形同天書,注釋是給自己看的,即使隔很長時間,也能清晰了解當時的思路;注釋也是給繼任者看的,使其能夠快速接替自己的工作。
10.【參考】好的命名、代碼結構是自解釋的,注釋力求精簡準确、表達到位。避免出現注釋的一個極端:過多過濫的注釋,代碼的邏輯一旦修改,修改注釋是相當大的負擔。
反例:
// put elephant into fridge put(elephant, fridge);
方法名 put,加上兩個有意義的變量名 elephant 和 fridge,已經說明了這是在幹什麼,語義清晰的代碼不需要額外的注釋。
11.【參考】特殊注釋标記,請注明标記人與标記時間。注意及時處理這些标記,通過标記掃描,經常清理此類标記。線上故障有時候就是來源于這些标記處的代碼。
1)待辦事宜(TODO):(标記人,标記時間,[預計處理時間])
表示需要實作,但目前還未實作的功能。這實際上是一個 Javadoc 的标簽,目前的 Javadoc
還沒有實作,但已經被廣泛使用。隻能應用于類,接口和方法(因為它是一個 Javadoc 标簽)。2)錯誤,不能工作(FIXME):(标記人,标記時間,[預計處理時間])
在注釋中用FIXME 标記某代碼是錯誤的,而且不能工作,需要及時糾正的情況。
17/38
阿裡巴巴 Java 開發手冊
(九) 其它
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. 【推薦】及時清理不再使用的代碼段或配置資訊。
說明:對于垃圾代碼或過時配置,堅決清理幹淨,避免程式過度臃腫,代碼備援。
正例:對于暫時被注釋掉,後續可能恢複使用的代碼片斷,在注釋代碼上方,統一規定使用三個斜杠(///)來說明注釋掉代碼的理由。
18/38
阿裡巴巴 Java 開發手冊
二、異常日志
(一) 異常處理
1. 【強制】Java 類庫中定義的可以通過預檢查方式規避的RuntimeException異常不應該通過
catch 的方式來處理,比如:NullPointerException,IndexOutOfBoundsException 等等。
說明:無法通過預檢查的異常除外,比如,在解析字元串形式的數字時,不得不通過 catch
NumberFormatException 來實作。
正例: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 是調用者的責任。即使被調用方法傳回空集合或者空對象,對調用
19/38
阿裡巴巴 Java 開發手冊
者來說,也并非高枕無憂,必須考慮到遠端調用失敗、序列化失敗、運作時異常等場景傳回
null 的情況。
10. 【推薦】防止 NPE,是程式員的基本修養,注意 NPE 産生的場景:
1)傳回類型為基本資料類型,return包裝資料類型的對象時,自動拆箱有可能産生NPE。
反例:publicint 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自定義異常,加入自己的了解的errormessage,對于調用
端解決問題的幫助不會太多。如果加了棧資訊,在頻繁調用出錯的情況下,資料序列化和傳輸
的性能損耗也是問題。
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);
20/38
阿裡巴巴 Java 開發手冊
2. 【強制】日志檔案至少儲存 15 天,因為有些異常具備以“周”為頻次發生的特點。
3. 【強制】應用中的擴充日志(如打點、臨時監控、通路日志等)命名方式:
appName_logType_logName.log。
logType:日志類型,如 stats/monitor/access 等;logName:日志描述。這種命名的好處:
通過檔案名就可知道日志檔案屬于什麼應用,什麼類型,什麼目的,也有利于歸類查找。
正例:mppserver應用中單獨監控時區轉換異常,如:
mppserver_monitor_timeZoneConvert.log
說明:推薦對日志進行分類,如将錯誤日志和業務日志分開存放,便于開發人員檢視,也便于
通過日志對系統進行及時監控。
4. 【強制】對 trace/debug/info 級别的日志輸出,必須使用條件輸出形式或者使用占位符的方式。
說明:logger.debug("Processingtrade 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。
正例:<loggername="com.taobao.dubbo.config" additivity="false">
6. 【強制】異常資訊應該包括兩類資訊:案發現場資訊和異常堆棧資訊。如果不處理,那麼通過關鍵字 throws 往上抛出。
正例:logger.error(各類參數或者對象 toString() + "_" + e.getMessage(), e);
7. 【推薦】謹慎地記錄日志。生産環境禁止輸出 debug 日志;有選擇地輸出 info 日志;如果使用 warn 來記錄剛上線時的業務行為資訊,一定要注意日志輸出量的問題,避免把伺服器磁盤撐爆,并記得及時删除這些觀察日志。
說明:大量地輸出無效日志,不利于系統性能提升,也不利于快速定位錯誤點。記錄日志時請思考:這些日志真的有人看嗎?看到這條日志你能做什麼?能不能給問題排查帶來好處?
8. 【推薦】可以使用 warn 日志級别來記錄使用者輸入參數錯誤的情況,避免使用者投訴時,無所适從。如非必要,請不要在此場景打出 error 級别,避免頻繁報警。
說明:注意日志輸出的級别,error 級别隻記錄系統邏輯出錯、異常或者重要的錯誤資訊。
9. 【推薦】盡量用英文來描述日志錯誤資訊,如果日志中的錯誤資訊用英文描述不清楚的話使用中文描述即可,否則容易産生歧義。國際化團隊或海外部署的伺服器由于字元集問題,【強制】使用全英文來注釋和描述日志錯誤資訊。
21/38
阿裡巴巴 Java 開發手冊
三、單元測試
1. 【強制】好的單元測試必須遵守 AIR 原則。
說明:單元測試線上上運作時,感覺像空氣(AIR)一樣并不存在,但在測試品質的保障上,卻是非常關鍵的。好的單元測試宏觀上來說,具有自動化、獨立性、可重複執行的特點。
l A:Automatic(自動化)
l I:Independent(獨立性)
l R:Repeatable(可重複)
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,都應該進行單元測試。
22/38
阿裡巴巴 Java 開發手冊
9. 【推薦】編寫單元測試代碼遵守 BCDE 原則,以保證被測試子產品的傳遞品質。
l B:Border,邊界值測試,包括循環邊界、特殊取值、特殊時間點、資料順序等。
l C:Correct,正确的輸入,并得到預期的結果。
l D:Design,與設計文檔相結合,來編寫單元測試。
l E:Error,強制錯誤資訊輸入(如:非法資料、異常流程、非業務允許輸入等),并得到預期的結果。
10.【推薦】對于資料庫相關的查詢,更新,删除等操作,不能假設資料庫裡的資料是存在的,或者直接操作資料庫把資料插入進去,請使用程式插入或者導入資料的方式來準備資料。
反例:删除某一行資料的單元測試,在資料庫中,先直接手動增加一行作為删除目标,但是這一行新增資料并不符合業務插入規則,導緻測試結果異常。
11. 【推薦】和資料庫相關的單元測試,可以設定自動復原機制,不給資料庫造成髒資料。或者
對單元測試産生的資料有明确的前字尾辨別。
正例:在 RDC 内部單元測試中,使用 RDC_UNIT_TEST_的字首辨別資料。
12.【推薦】對于不可測的代碼建議做必要的重構,使代碼變得可測,避免為了達到測試要求而書寫不規範測試代碼。
13.【推薦】在設計評審階段,開發人員需要和測試人員一起确定單元測試範圍,單元測試最好覆寫所有測試用例。
14.【推薦】單元測試作為一種品質保障手段,不建議項目釋出後補充單元測試用例,建議在項目提測前完成單元測試。
15. 【參考】為了更友善地進行單元測試,業務代碼應避免以下情況:
l 構造方法中做的事情過多。
l 存在過多的全局變量和靜态方法。
l 存在過多的外部依賴。
l 存在過多的條件語句。
說明:多層條件語句建議使用衛語句、政策模式、狀态模式等方式重構。
16. 【參考】不要對單元測試存在如下誤解:
l 那是測試同學幹的事情。本文是開發手冊,凡是本文内容都是與開發同學強相關的。
l 單元測試代碼是多餘的。系統的整體功能與各單元部件的測試正常與否是強相關的。
l 單元測試代碼不需要維護。一年半載後,那麼單元測試幾乎處于廢棄狀态。
l 單元測試與線上故障沒有辯證關系。好的單元測試能夠最大限度地規避線上故障。
23/38
阿裡巴巴 Java 開發手冊
四、安全規約
1. 【強制】隸屬于使用者個人的頁面或者功能必須進行權限控制校驗。
說明:防止沒有做水準權限校驗就可随意通路、修改、删除别人的資料,比如檢視他人的私信内容、修改他人的訂單。
2. 【強制】使用者敏感資料禁止直接展示,必須對展示資料進行脫敏。
說明:中國大陸個人手機号碼顯示為:158****9119,隐藏中間 4 位,防止隐私洩露。
3. 【強制】使用者輸入的 SQL 參數嚴格使用參數綁定或者 METADATA 字段值限定,防止 SQL 注入,禁止字元串拼接 SQL 通路資料庫。
4. 【強制】使用者請求傳入的任何參數必須做有效性驗證。說明:忽略參數校驗可能導緻:
l page size 過大導緻記憶體溢出
l 惡意 order by 導緻資料庫慢查詢
l 任意重定向
l SQL 注入
l 反序列化注入
l 正則輸入源串拒絕服務 ReDoS
說明:Java 代碼用正則來驗證用戶端的輸入,有些正則寫法驗證普通使用者輸入沒有問題,
但是如果攻擊人員使用的是特殊構造的字元串來驗證,有可能導緻死循環的結果。
5. 【強制】禁止向 HTML 頁面輸出未經安全過濾或未正确轉義的使用者資料。
6. 【強制】表單、AJAX 送出必須執行 CSRF 安全驗證。
說明:CSRF(Cross-siterequest forgery)跨站請求僞造是一類常見程式設計漏洞。對于存在
CSRF 漏洞的應用/網站,攻擊者可以事先構造好 URL,隻要受害者使用者一通路,背景便在使用者不知情的情況下對資料庫中使用者參數進行相應修改。
7. 【強制】在使用平台資源,譬如短信、郵件、電話、下單、支付,必須實作正确的防重放的機制,如數量限制、疲勞度控制、驗證碼校驗,避免被濫刷而導緻資損。
說明:如注冊時發送驗證碼到手機,如果沒有限制次數和頻率,那麼可以利用此功能騷擾到其它使用者,并造成短信平台資源浪費。
8. 【推薦】發貼、評論、發送即時消息等使用者生成内容的場景必須實作防刷、文本内容違禁詞過濾等風控政策。
24/38
阿裡巴巴 Java 開發手冊
五、MySQL 資料庫
(一) 建表規約
1. 【強制】表達是與否概念的字段,必須使用 is_xxx 的方式命名,資料類型是 unsigned tinyint (1 表示是,0 表示否)。
說明:任何字段如果為非負數,必須是 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
3. 【強制】表名不使用複數名詞。
說明:表名應該僅僅表示表裡面的實體内容,不應該表示實體數量,對應于 DO 類名也是單數形式,符合表達習慣。
4. 【強制】禁用保留字,如 desc、range、match、delayed 等,請參考MySQL官方保留字。
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, gmt_create, gmt_modified。
說明:其中 id 必為主鍵,類型為 bigint unsigned、單表時自增、步長為1。gmt_create, gmt_modified 的類型均為 datetime 類型,前者現在時表示主動建立,後者過去分詞表示被動更新。
25/38
阿裡巴巴 Java 開發手冊
10. 【推薦】表的命名最好是加上“業務名稱_表的作用”。
正例:alipay_task/ force_project / trade_config
11. 【推薦】庫名與應用名稱盡量一緻。
12. 【推薦】如果修改字段含義或對字段表示的狀态追加時,需要及時更新字段注釋。
13. 【推薦】字段允許适當備援,以提高查詢性能,但必須考慮資料一緻。備援字段應遵循:
1)不是頻繁修改的字段。
2)不是varchar超長字段,更不能是text字段。
正例:商品類目名稱使用頻率高,字段長度短,名稱基本一成不變,可在相關聯的表中備援存
儲類目名稱,避免關聯查詢。
14. 【推薦】單表行數超過 500 萬行或者單表容量超過 2GB,才推薦進行分庫分表。
說明:如果預計三年後的資料量根本達不到這個級别,請不要在建立表時就分庫分表。
15. 【參考】合适的字元存儲長度,不但節約資料庫表空間、節約索引存儲,更重要的是提升檢索速度。
正例:如下表,其中無符号值可以避免誤存負數,且擴大了表示範圍。
(二) 索引規約
1. 【強制】業務上具有唯一特性的字段,即使是多個字段的組合,也必須建成唯一索引。
說明:不要以為唯一索引影響了 insert 速度,這個速度損耗可以忽略,但提高查找速度是明顯的;另外,即使在應用層做了非常完善的校驗控制,隻要沒有唯一索引,根據墨菲定律,必然有髒資料産生。
2. 【強制】超過三個表禁止 join。需要 join 的字段,資料類型必須絕對一緻;多表關聯查詢時,保證被關聯的字段需要有索引。
說明:即使雙表 join 也要注意表索引、SQL 性能。
3. 【強制】在 varchar 字段上建立索引時,必須指定索引長度,沒必要對全字段建立索引,根據實際文本區分度決定索引長度即可。
26/38
阿裡巴巴 Java 開發手冊
說明:索引的長度與區分度是一對沖突體,一般對字元串類型資料,長度為 20 的索引,區分度會高達 90%以上,可以使用count(distinctleft(列名, 索引長度))/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
8. 【推薦】 SQL 性能優化的目标:至少要達到 range 級别,要求是 ref 級别,如果可以是 consts
最好。
說明:
1)consts單表中最多隻有一個比對行(主鍵或者唯一索引),在優化階段即可讀取到資料。
2)ref指的是使用普通的索引(normal index)。
3)range對索引進行範圍檢索。
反例:explain表的結果,type=index,索引實體檔案全掃描,速度非常慢,這個 index 級
别比較range 還低,與全表掃描是小巫見大巫。
9. 【推薦】建組合索引的時候,區分度最高的在最左邊。
正例:如果 where a=? and b=?,如果 a 列的幾乎接近于唯一值,那麼隻需要單建 idx_a 索引即可。
說明:存在非等号和等号混合時,在建索引時,請把等号條件的列前置。如:where c>? and d=?那麼即使 c 的區分度更高,也必須把 d 放在索引的最前列,即索引idx_d_c。
27/38
阿裡巴巴 Java 開發手冊
10. 【推薦】防止因字段類型不同造成的隐式轉換,導緻索引失效。
11. 【參考】建立索引時避免有如下極端誤解:
1)甯濫勿缺。認為一個查詢就需要建一個索引。
2)甯缺勿濫。認為索引會消耗空間、嚴重拖慢更新和新增速度。
3)抵制惟一索引。認為業務的惟一性一律需要在應用層通過“先查後插”方式解決。
(三) 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 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應直接傳回,避免執行後面的分頁語句。
6. 【強制】不得使用外鍵與級聯,一切外鍵概念必須在應用層解決。
說明:以學生和成績的關系為例,學生表中的 student_id 是主鍵,那麼成績表中的 student_id 則為外鍵。如果更新學生表中的 student_id,同時觸發成績表中的 student_id 更新,即為級聯更新。外鍵與級聯更新适用于單機低并發,不适合分布式、高并發叢集;級聯更新是強阻塞,存在資料庫更新風暴的風險;外鍵影響資料庫的插入速度。
7. 【強制】禁止使用存儲過程,存儲過程難以調試和擴充,更沒有移植性。
8. 【強制】資料訂正(特别是删除、修改記錄操作)時,要先 select,避免出現誤删除,确認無誤才能執行更新語句。
9. 【推薦】in 操作能避免則避免,若實在避免不了,需要仔細評估 in 後邊的集合元素數量,控制在 1000 個之内。
28/38
阿裡巴巴 Java 開發手冊
10.【參考】如果有國際化需要,所有的字元存儲與表示,均以 utf-8編碼,注意字元統計函數的差別。
說明:
SELECT LENGTH("輕松工作");傳回為12
SELECT CHARACTER_LENGTH("輕松工作");傳回為4
如果需要存儲表情,那麼選擇utf8mb4來進行存儲,注意它與 utf-8編碼的差別。
11. 【參考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系統和事務日志資源少,但 TRUNCATE
無事務且不觸發trigger,有可能造成事故,故不建議在開發代碼中使用此語句。
說明:TRUNCATETABLE 在功能上與不帶WHERE 子句的DELETE 語句相同。
(四) ORM 映射
1. 【強制】在表查詢中,一律不要使用 * 作為查詢的字段清單,需要哪些字段必須明确寫明。說明:1)增加查詢分析器解析成本。2)增減字段容易與resultMap配置不一緻。3)無用字段增加網絡消耗,尤其是 text 類型的字段。
2. 【強制】POJO 類的布爾屬性不能加 is,而資料庫字段必須加 is_,要求在 resultMap 中進行字段與屬性之間的映射。
說明:參見定義 POJO 類以及資料庫字段定義規定,在<resultMap>中增加映射,是必須的。在 MyBatis Generator 生成的代碼中,需要進行對應的修改。
3. 【強制】不要用 resultClass 當傳回參數,即使所有類屬性名與資料庫字段一一對應,也需要定義;反過來,每一個表也必然有一個 POJO 類與之對應。
說明:配置映射關系,使字段與 DO 類解耦,友善維護。
4. 【強制】sql.xml配置參數使用:#{},#param# 不要使用${} 此種方式容易出現SQL注入。
5. 【強制】iBATIS 自帶的 queryForList(String statementName,intstart,int size)不推薦使用。
說明:其實作方式是在資料庫取到 statementName 對應的 SQL 語句的所有記錄,再通過 subList
取 start,size的子集合。
正例:Map<String,Object> map = new HashMap<>();
map.put("start", start);
map.put("size", size);
6. 【強制】不允許直接拿 HashMap 與 Hashtable 作為查詢結果集的輸出。
說明:resultClass=”Hashtable”,會置入字段名和屬性值,但是值的類型不可控。
7. 【強制】更新資料表記錄時,必須同時更新記錄對應的 gmt_modified 字段值為目前時間。
29/38
阿裡巴巴 Java 開發手冊
8. 【推薦】不要寫一個大而全的資料更新接口。傳入為 POJO 類,不管是不是自己的目标更新字段,都進行 update table set c1=value1,c2=value2,c3=value3; 這是不對的。執行 SQL
時,不要更新無改動的字段,一是易出錯;二是效率低;三是增加 binlog 存儲。
9. 【參考】@Transactional 事務不要濫用。事務會影響資料庫的 QPS,另外使用事務的地方需要考慮各方面的復原方案,包括緩存復原、搜尋引擎復原、消息補償、統計修正等。
10.【參考】<isEqual>中的compareValue是與屬性值對比的常量,一般是數字,表示相等時帶上此條件;<isNotEmpty>表示不為空且不為null時執行;<isNotNull>表示不為null值時執行。
30/38
阿裡巴巴 Java 開發手冊
六、工程結構
(一) 應用分層
1. 【推薦】圖中預設上層依賴于下層,箭頭關系表示可直接依賴,如:開放接口層可以依賴于Web 層,也可以直接依賴于 Service 層,依此類推:
· 開放接口層:可直接封裝 Service 方法暴露成 RPC 接口;通過 Web 封裝成 http 接口;進行網關安全控制、流量控制等。
· 終端顯示層:各個端的模闆渲染并執行顯示的層。目前主要是 velocity 渲染,JS 渲染, JSP 渲染,移動端展示等。
· Web層:主要是對通路控制進行轉發,各類基本參數校驗,或者不複用的業務簡單處理等。
· Service層:相對具體的業務邏輯服務層。
· Manager層:通用業務處理層,它有如下特征:
1)對第三方平台封裝的層,預處理傳回結果及轉化異常資訊;
2)對Service層通用能力的下沉,如緩存方案、中間件通用處理;
3)與DAO層互動,對多個DAO的組合複用。
· DAO層:資料通路層,與底層MySQL、Oracle、Hbase等進行資料互動。
· 外部接口或第三方平台:包括其它部門 RPC 開放接口,基礎平台,其它公司的 HTTP 接口。
2. 【參考】(分層異常處理規約)在 DAO 層,産生的異常類型有很多,無法用細粒度的異常進行 catch,使用 catch(Exception e)方式,并 throw new DAOException(e),不需要列印日志,因為日志在 Manager/Service 層一定需要捕獲并列印到日志檔案中去,如果同台伺服器再打日志,浪費性能和存儲。在 Service 層出現異常時,必須記錄出錯日志到磁盤,盡可能帶上參數資訊,相當于保護案發現場。如果 Manager 層與 Service 同機部署,日志方式與 DAO 層處理一緻,如果是單獨部署,則采用與 Service 一緻的處理方式。Web 層絕不應該繼續往上抛異常,因為已經處于頂層,如果意識到這個異常将導緻頁面無法正常渲染,那麼就應該直接
31/38
阿裡巴巴 Java 開發手冊
跳轉到友好錯誤頁面,加上使用者容易了解的錯誤提示資訊。開放接口層要将異常處理成錯誤碼和錯誤資訊方式傳回。
3. 【參考】分層領域模型規約:
· DO(Data Object):此對象與資料庫表結構一一對應,通過DAO層向上傳輸資料源對象。
· DTO(Data Transfer Object):資料傳輸對象,Service或Manager向外傳輸的對象。
· BO(Business Object):業務對象,由Service層輸出的封裝業務邏輯的對象。
· AO(Application Object):應用對象,在Web層與Service層之間抽象的複用對象模型,極為貼近展示層,複用度不高。
· VO(View Object):顯示層對象,通常是Web向模闆渲染引擎層傳輸的對象。
· Query:資料查詢對象,各層接收上層的查詢請求。注意超過2個參數的查詢封裝,禁止使用 Map 類來傳輸。
(二) 二方庫依賴
1. 【強制】定義 GAV 遵從以下規則:
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:詳細規定參考下方。
2. 【強制】二方庫版本号命名方式:主版本号.次版本号.修訂号
1) 主版本号:産品方向改變,或者大規模API不相容,或者架構不相容更新。
2) 次版本号:保持相對相容性,增加主要功能特性,影響範圍極小的API不相容修改。
3) 修訂号:保持完全相容性,修複BUG、新增次要功能特性等。
說明:注意起始版本号必須為:1.0.0,而不是0.0.1 正式釋出的類庫必須先去中央倉庫進
行查證,使版本号有延續性,正式版本号不允許覆寫更新。如目前版本:1.3.3,那麼下一個
合理的版本号:1.3.4或 1.4.0 或 2.0.0
3. 【強制】線上應用不要依賴 SNAPSHOT 版本(安全包除外)。
說明:不依賴 SNAPSHOT 版本是保證應用釋出的幂等性。另外,也可以加快編譯時的打包建構。
4. 【強制】二方庫的新增或更新,保持除功能點之外的其它 jar 包仲裁結果不變。如果有改變,必須明确評估和驗證,建議進行 dependency:resolve 前後資訊比對,如果仲裁結果完全不一緻,那麼通過 dependency:tree 指令,找出差異點,進行<excludes>排除 jar 包。
32/38
阿裡巴巴 Java 開發手冊
5. 【強制】二方庫裡可以定義枚舉類型,參數可以使用枚舉類型,但是接口傳回值不允許使用枚舉類型或者包含枚舉類型的 POJO 對象。
6. 【強制】依賴于一個二方庫群時,必須定義一個統一的版本變量,避免版本号不一緻。
說明:依賴 springframework-core,-context,-beans,它們都是同一個版本,可以定義一
個變量來儲存版本:${spring.version},定義依賴的時候,引用該版本。
7. 【強制】禁止在子項目的 pom 依賴中出現相同的 GroupId,相同的 ArtifactId,但是不同的
Version。
說明:在本地調試時會使用各子項目指定的版本号,但是合并成一個 war,隻能有一個版本号
出現在最後的lib 目錄中。可能出現線下調試是正确的,釋出到線上卻出故障的問題。
8. 【推薦】所有 pom 檔案中的依賴聲明放在<dependencies>語句塊中,所有版本仲裁放在
<dependencyManagement>語句塊中。
說明:<dependencyManagement>裡隻是聲明版本,并不實作引入,是以子項目需要顯式的聲明依賴,version 和 scope 都讀取自父 pom。而<dependencies>所有聲明在主 pom 的
<dependencies>裡的依賴都會自動引入,并預設被所有的子項目繼承。
9. 【推薦】二方庫不要有配置項,最低限度不要再增加配置項。
10. 【參考】為避免應用二方庫的依賴沖突問題,二方庫釋出者應當遵循以下原則:
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,當并發連接配接數很大時很
33/38
阿裡巴巴 Java 開發手冊
容易因為fd 不足而出現“open too many files”錯誤,導緻新的連接配接無法建立。 建議将 linux 伺服器所支援的最大句柄數調高數倍(與伺服器的記憶體數量相關)。
3. 【推薦】給 JVM 環境參數設定-XX:+HeapDumpOnOutOfMemoryError參數,讓 JVM 碰到 OOM 場
景時輸出dump 資訊。
說明:OOM的發生是有機率的,甚至相隔數月才出現一例,出錯時的堆内資訊對解決問題非常有幫助。
4. 【推薦】線上上生産環境,JVM 的 Xms 和 Xmx 設定一樣大小的記憶體容量,避免在 GC 後調整堆大小帶來的壓力。
5. 【參考】伺服器内部重定向使用 forward;外部重定向位址使用 URL 拼裝工具類來生成,否則會帶來 URL 維護不一緻的問題和潛在的安全風險。
34/38
阿裡巴巴 Java 開發手冊
七、設計規約
1. 【強制】存儲方案和底層資料結構的設計獲得評審一緻通過,并沉澱成為文檔。
說明:有缺陷的底層資料結構容易導緻系統風險上升,可擴充性下降,重構成本也會因曆史資料遷移和系統平滑過渡而陡然增加,是以,存儲方案和資料結構需要認真地進行設計和評審,生産環境送出執行後,需要進行 double check。
正例:評審内容包括存儲媒體選型、表結構設計能否滿足技術方案、存取性能和存儲空間能否滿足業務發展、表或字段之間的辯證關系、字段名稱、字段類型、索引等;資料結構變更(如在原有表中新增字段)也需要進行評審通過後上線。
2. 【強制】在需求分析階段,如果與系統互動的User 超過一類并且相關的 User Case 超過5個,使用用例圖來表達更加清晰的結構化需求。
3. 【強制】如果某個業務對象的狀态超過 3個,使用狀态圖來表達并且明确狀态變化的各個觸發條件。
說明:狀态圖的核心是對象狀态,首先明确對象有多少種狀态,然後明确兩兩狀态之間是否存在直接轉換關系,再明确觸發狀态轉換的條件是什麼。
正例:淘寶訂單狀态有已下單、待付款、已付款、待發貨、已發貨、已收貨等。比如已下單與已收貨這兩種狀态之間是不可能有直接轉換關系的。
4. 【強制】如果系統中某個功能的調用鍊路上的涉及對象超過 3個,使用時序圖來表達并且明确各調用環節的輸入與輸出。
說明:時序圖反映了一系列對象間的互動與協作關系,清晰立體地反映系統的調用縱深鍊路。
5. 【強制】如果系統中模型類超過 5個,并且存在複雜的依賴關系,使用類圖來表達并且明确類之間的關系。
說明:類圖像建築領域的施工圖,如果搭平房,可能不需要,但如果建造螞蟻Z空間大樓,肯定需要詳細的施工圖。
6. 【強制】如果系統中超過 2個對象之間存在協作關系,并且需要表示複雜的處理流程,使用活動圖來表示。
說明:活動圖是流程圖的擴充,增加了能夠展現協作關系的對象泳道,支援表示并發等。
7. 【推薦】需求分析與系統設計在考慮主幹功能的同時,需要充分評估異常流程與業務邊界。反例:使用者在淘寶付款過程中,銀行扣款成功,發送給使用者扣款成功短信,但是支付寶入款時由于斷網演練産生異常,淘寶訂單頁面依然顯示未付款,導緻使用者投訴。
35/38
阿裡巴巴 Java 開發手冊
8. 【推薦】類在設計與實作時要符合單一原則。
說明:單一原則最易了解卻是最難實作的一條規則,随着系統演進,很多時候,忘記了類設計的初衷。
9. 【推薦】謹慎使用繼承的方式來進行擴充,優先使用聚合/組合的方式來實作。
說明:不得已使用繼承的話,必須符合裡氏代換原則,此原則說父類能夠出現的地方子類一定能夠出現,比如,“把錢交出來”,錢的子類美元、歐元、人民币等都可以出現。
10.【推薦】系統設計時,根據依賴倒置原則,盡量依賴抽象類與接口,有利于擴充與維護。
說明:低層次子產品依賴于高層次子產品的抽象,友善系統間的解耦。
11.【推薦】系統設計時,注意對擴充開放,對修改閉合。
說明:極端情況下,傳遞的代碼都是不可修改的,同一業務域内的需求變化,通過子產品或類的擴充來實作。
12.【推薦】系統設計階段,共性業務或公共行為抽取出來公共子產品、公共配置、公共類、公共方法等,避免出現重複代碼或重複配置的情況。
說明:随着代碼的重複次數不斷增加,維護成本指數級上升。
13. 【推薦】避免如下誤解:靈活開發=講故事+編碼+釋出。
說明:靈活開發是快速傳遞疊代可用的系統,省略多餘的設計方案,摒棄傳統的審批流程,但核心關鍵點上的必要設計和文檔沉澱是需要的。
反例:某團隊為了業務快速發展,靈活成了産品經理催進度的借口,系統中均是勉強能運作但像面條一樣的代碼,可維護性和可擴充性極差,一年之後,不得不進行大規模重構,得不償失。
14.【參考】系統設計主要目的是明确需求、理順邏輯、後期維護,次要目的用于指導編碼。
說明:避免為了設計而設計,系統設計文檔有助于後期的系統維護,是以設計結果需要進行分類歸檔儲存。
15.【參考】設計的本質就是識别和表達系統難點,找到系統的變化點,并隔離變化點。
說明:世間衆多設計模式目的是相同的,即隔離系統變化點。
16. 【參考】系統架構設計的目的:
l 确定系統邊界。确定系統在技術層面上的做與不做。
l 确定系統内子產品之間的關系。确定子產品之間的依賴關系及子產品的宏觀輸入與輸出。
l 确定指導後續設計與演化的原則。使後續的子系統或子產品設計在規定的架構内繼續演化。
l 确定非功能性需求。非功能性需求是指安全性、可用性、可擴充性等。
36/38
阿裡巴巴 Java 開發手冊
附 1:版本曆史
37/38
阿裡巴巴 Java 開發手冊
附 2:專有名詞解釋
1. POJO(Plain Ordinary Java Object): 在本手冊中,POJO 專指隻有 setter / getter / toString 的簡單類,包括 DO/DTO/BO/VO 等。
2. GAV(GroupId、ArtifactctId、Version): Maven 坐标,是用來唯一辨別 jar 包。
3. OOP(Object Oriented Programming): 本手冊泛指類、對象的程式設計處理方式。
4. ORM(Object Relation Mapping): 對象關系映射,對象領域模型與底層資料之間的轉換,
本文泛指iBATIS, mybatis 等架構。
5. NPE(java.lang.NullPointerException): 空指針異常。
6. SOA(Service-Oriented Architecture): 面向服務架構,它可以根據需求通過網絡對松散
耦合的粗粒度應用元件進行分布式部署、組合和使用,有利于提升元件可重用性,可維護性。
7. IDE(Integrated Development Environment): 用于提供程式開發環境的應用程式,一般
包括代碼編輯器、編譯器、調試器和圖形使用者界面等工具,本《手冊》泛指 IntelliJ IDEA
和eclipse。
8. OOM(Out Of Memory): 源于 java.lang.OutOfMemoryError,當 JVM 沒有足夠的記憶體來為對象配置設定空間并且垃圾回收器也無法回收空間時,系統出現的嚴重狀況。
9. 一方庫: 本工程内部子項目子產品依賴的庫(jar 包)。
10. 二方庫: 公司内部釋出到中央倉庫,可供公司内部其它應用依賴的庫(jar 包)。
11. 三方庫: 公司之外的開源庫(jar 包)。
38/38
阿裡巴巴 Java 開發手冊
39/38