程式設計規約
命名規約
- 【強制】 代碼中的命名均不能以下劃線或美元符号開始,也不能以下劃線或美元符号結束。
反例: _name / __name / $Object / name_ / name$ / Object$
- 【強制】 代碼中的命名嚴禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。
說明: 正确的英文拼寫和文法可以讓閱讀者易于了解,避免歧義。注意,即使純拼音命名方式也要避免采用。
反例: DaZhePromotion [打折] / getPingfenByName() [評分] / int 某變量 = 3
正例: alibaba / taobao / youku / hangzhou 等國際通用的名稱, 可視同英文
- 【強制】類名使用 UpperCamelCase 風格,必須遵從駝峰形式,但以下情形例外:( 領域模型的相關命名) DO / BO / DTO / VO 等。
正例: MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例: macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
- 【強制】方法名、參數名、成員變量、局部變量都統一使用 lowerCamelCase 風格,必須遵從駝峰形式。
正例: localValue / getHttpMessage() / inputUserId
- 【 強制】常量命名全部大寫,單詞間用下劃線隔開,力求語義表達完整清楚,不要嫌名字長。
正例: MAX_STOCK_COUNT
反例: MAX_COUNT
- 【強制】抽象類命名使用 Abstract 或 Base 開頭; 異常類命名使用 Exception 結尾; 測試類命名以它要測試的類的名稱開始,以 Test 結尾。
- 【強制】中括号是數組類型的一部分,數組定義如下: String[] args;
反例: 請勿使用 String args[]的方式來定義。
- 【強制】 POJO 類中布爾類型的變量,都不要加 is,否則部分架構解析會引起序列化錯誤。
反例: 定義為基本資料類型 boolean isSuccess; 的屬性,它的方法也是 isSuccess(), RPC架構在反向解析的時候, “以為”對應的屬性名稱是 success,導緻屬性擷取不到,進而抛出異 常。
- 【強制】包名統一使用小寫,點分隔符之間有且僅有一個自然語義的英語單詞。包名統一使用單數形式,但是類名如果有複數含義,類名可以使用複數形式。
正例: 應用工具類包名為 com.alibaba.open.util、類名為 MessageUtils( 此規則參考spring 的架構結構)
- 【強制】杜絕完全不規範的縮寫, 避免望文不知義。
反例: AbstractClass“ 縮寫” 命名成 AbsClass; condition“ 縮寫” 命名成 condi,此類随意縮寫嚴重降低了代碼的可閱讀性。
- 【推薦】如果使用到了設計模式,建議在類名中展現出具體模式
說明: 将設計模式展現在名字中,有利于閱讀者快速了解架構設計思想。
正例:
public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;
- 【推薦】接口類中的方法和屬性不要加任何修飾符号( public 也不要加) ,保持代碼的簡潔性,并加上有效的 Javadoc 注釋。盡量不要在接口裡定義變量,如果一定要定義變量,肯定是與接口方法相關,并且是整個應用的基礎常量。
正例: 接口方法簽名: void f(); 接口基礎常量表示: String COMPANY = "alibaba";
反例: 接口方法定義: public abstract void f();
說明: JDK8 中接口允許有預設實作,那麼這個 default 方法,是對所有實作類都有價值的預設實作。
-
【推薦】接口和實作類的命名有兩套規則:
1) 【強制】對于 Service 和 DAO 類,基于 SOA 的理念,暴露出來的服務一定是接口,内部
的實作類用 Impl 的字尾與接口差別。
2) 【推薦】 如果是形容能力的接口名稱,取對應的形容詞做接口名 ( 通常是–able 的形式)。正例: CacheServiceImpl 實作 CacheService 接口。
正例: AbstractTranslator 實作 Translatable。
-
【參考】枚舉類名建議帶上 Enum 字尾,枚舉成員名稱需要全大寫,單詞間用下劃線隔開。
說明: 枚舉其實就是特殊的常量類,且構造方法被預設強制是私有。
正例: 枚舉名字: DealStatusEnum, 成員名稱: SUCCESS / UNKOWN_REASON。
- 【參考】各層命名規約:
1 2 3 4 5 6 7 8 9 10 11 12 A) Service/DAO 層方法命名規約 1) 擷取單個對象的方法用 get 做字首。 2) 擷取多個對象的方法用 list 做字首。 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 2 3 4 5 DO( Data Object) :與資料庫表結構一一對應,通過 DAO 層向上傳輸資料源對象。 DTO( Data Transfer Object) :資料傳輸對象, Service 和 Manager 向外傳輸的對象。 BO( Business Object) :業務對象。 可以由 Service 層輸出的封裝業務邏輯的對象。 QUERY:資料查詢對象,各層接收上層的查詢請求。 注:超過 2 個參數的查詢封裝,禁止使用 Map 類來傳輸。 VO( View Object) :顯示層對象,通常是 Web 向模闆渲染引擎層傳輸的對象。
常量定義
- 【強制】不允許出現任何魔法值( 即未經定義的常量) 直接出現在代碼中。
反例: String key="Id#taobao_"+tradeId; cache.put(key, value);
-
【強制】 long 或者 Long 初始指派時,必須使用大寫的 L,不能是小寫的 l,小寫容易跟數字
1 混淆,造成誤解。
說明: Long a = 2l; 寫的是數字的 21,還是 Long 型的 2?
-
【推薦】不要使用一個常量類維護所有常量,應該按常量功能進行歸類,分開維護。
如:緩存相關的常量放在類: CacheConsts 下; 系統配置相關的常量放在類: ConfigConsts 下。
說明: 大而全的常量類,非得使用查找功能才能定位到修改的常量,不利于了解和維護。
-
【推薦】常量的複用層次有五層:跨應用共享常量、應用内共享常量、子工程内共享常量、包内共享常量、類内共享常量。
1) 跨應用共享常量:放置在二方庫中,通常是 client.jar 中的 constant 目錄下。
2) 應用内共享常量:放置在一方庫的 modules 中的 constant 目錄下。
反例: 易懂變量也要統一定義成應用内共享常量,兩位攻城師在兩個類中分别定義了表示“是”的變量: 類 A 中: public static final String YES = "yes"; 類 B 中: public static final String YES = "y"; A.YES.equals(B.YES),預期是 true,但實際傳回為 false,導緻産生線上問題。
3) 子工程内部共享常量:即在目前子工程的 constant 目錄下。
4) 包内共享常量:即在目前包下單獨的 constant 目錄下。
5) 類内共享常量:直接在類内部 private static final 定義。
- 【推薦】如果變量值僅在一個範圍内變化用 Enum 類。如果還帶有名稱之外的延伸屬性,必須使用 Enum 類,下面正例中的數字就是延伸資訊,表示星期幾。
正例: public Enum{ MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5),SATURDAY(6), SUNDAY(7); }
格式規範
-
【強制】大括号的使用約定。如果是大括号内為空,則簡潔地寫成{}即可,不需要換行; 如果是非空代碼塊則:
1) 左大括号前不換行。
2) 左大括号後換行。
3) 右大括号前換行。
4) 右大括号後還有 else 等代碼則不換行; 表示終止右大括号後必須換行。
-
【強制】 左括号和後一個字元之間不出現空格; 同樣,右括号和前一個字元之間也不出現空
格。詳見第 5 條下方正例提示。
- 【強制】 if/for/while/switch/do 等保留字與左右括号之間都必須加空格。
- 【強制】任何運算符左右必須加一個空格。
說明: 運算符包括指派運算符=、邏輯運算符&&、加減乘除符号、三目運作符等。
- 【強制】 縮進采用 4 個空格,禁止使用 tab 字元。
正例: ( 涉及 1-5 點)說明: 如果使用 tab 縮進,必須設定 1 個 tab 為 4 個空格。 IDEA 設定 tab 為 4 個空格時, 請勿勾選 Use tab character;而在 eclipse 中,必須勾選 insert spaces for tabs。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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"); // 在右大括号後直接結束,則必須換行 } } -
【強制】單行字元數限制不超過 120 個,超出需要換行,換行時遵循如下原則:
1) 第二行相對第一行縮進 4 個空格,從第三行開始,不再繼續縮進,參考示例。
2) 運算符與下文一起換行。
3) 方法調用的點符号與下文一起換行。
4) 在多個參數超長,逗号後進行換行。
5) 在括号前不要換行,見反例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 正例: 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); - 【強制】方法參數在定義和傳入時,多個參數逗号後邊必須加空格。
正例: 下例中實參的"a",後邊必須要有一個空格。 method("a", "b", "c");
- 【強制】 IDE 的 text file encoding 設定為 UTF-8; IDE 中檔案的換行符使用 Unix 格式,不要使用 windows 格式。
- 【推薦】沒有必要增加若幹空格來使某一行的字元與上一行的相應字元對齊。
1 2 3 4 5 6 7 正例: int a = 3; long b = 4L; float c = 5F; StringBuffer sb = new StringBuffer(); 說明: 增加 sb 這個變量,如果需要對齊,則給 a、 b、 c 都要增加幾個空格,在變量比較多的情況下,是一種累贅的事情 - 【推薦】方法體内的執行語句組、變量的定義語句組、不同的業務邏輯之間或者不同的語義之間插入一個空行。相同業務邏輯和語義之間不需要插入空行。
說明: 沒有必要插入多行空格進行隔開。
OOP 規約
- 【強制】避免通過一個類的對象引用通路此類的靜态變量或靜态方法,無謂增加編譯器解析成本,直接用類名來通路即可。
- 【強制】所有的覆寫方法,必須加@Override 注解。
反例: getObject()與 get0bject()的問題。一個是字母的 O,一個是數字的 0,加@Override可以準确判斷是否覆寫成功。另外,如果在抽象類中對方法簽名進行修改,其實作類會馬上編譯報錯。
- 【強制】相同參數類型,相同業務含義,才可以使用 Java 的可變參數,避免使用 Object。
說明: 可變參數必須放置在參數清單的最後。 ( 提倡同學們盡量不用可變參數程式設計)
正例: public User getUsers(String type, Integer... ids)
- 【強制】對外暴露的接口簽名,原則上不允許修改方法簽名,避免對接口調用方産生影響。接口過時必須加@Deprecated 注解,并清晰地說明采用的新接口或者新服務是什麼。
- 【強制】不能使用過時的類或方法。
說明: java.net.URLDecoder 中的方法 decode(String encodeStr) 這個方法已經過時,應該使用雙參數 decode(String source, String encode)。接口提供方既然明确是過時接口,那麼有義務同時提供新的接口; 作為調用方來說,有義務去考證過時方法的新實作是什麼。
- 【強制】 Object 的 equals 方法容易抛空指針異常,應使用常量或确定有值的對象來調用equals。
正例: "test".equals(object);
反例: object.equals("test");
說明: 推薦使用 java.util.Objects#equals ( JDK7 引入的工具類)
- 【強制】所有的相同類型的包裝類對象之間值的比較,全部使用 equals 方法比較。
說明: 對于 Integer var=?在-128 至 127 之間的指派, Integer 對象是在IntegerCache.cache 産生,會複用已有對象,這個區間内的 Integer 值可以直接使用==進行判斷,但是這個區間之外的所有資料,都會在堆上産生,并不會複用已有對象,這是一個大坑, 推薦使用 equals 方法進行判斷
-
【強制】關于基本資料類型與包裝資料類型的使用标準如下:
1) 所有的 POJO 類屬性必須使用包裝資料類型。
2) RPC 方法的傳回值和參數必須使用包裝資料類型。
3) 所有的局部變量【 推薦】 使用基本資料類型。
1 2 3 4 5 說明: POJO 類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行指派,任何NPE 問題,或者入庫檢查,都由使用者來保證。 正例: 資料庫的查詢結果可能是 null,因為自動拆箱,用基本資料類型接收有 NPE 風險。 反例: 比如顯示成交總額漲跌情況,即正負 x%, x 為基本資料類型,調用的 RPC 服務,調用不成功時,傳回的是預設值,頁面顯示: 0%,這是不合理的,應該顯示成中劃線-。是以包裝資料類型的 null 值,能夠表示額外的資訊, 如:遠端調用失敗,異常退出。 - 【強制】定義 DO/DTO/VO 等 POJO 類時,不要設定任何屬性預設值。
反例: POJO 類的 gmtCreate 預設值為 new Date();但是這個屬性在資料提取時并沒有置入具體值,在更新其它字段時又附帶更新了此字段,導緻建立時間被修改成目前時間。
- 【強制】序列化類新增屬性時,請不要修改 serialVersionUID 字段,避免反序列失敗; 如果完全不相容更新,避免反序列化混亂,那麼請修改 serialVersionUID 值。
說明: 注意 serialVersionUID 不一緻會抛出序列化運作時異常。
- 【強制】構造方法裡面禁止加入任何業務邏輯,如果有初始化邏輯,請放在 init 方法中。
- 【強制】 POJO 類必須寫 toString 方法。使用 IDE 的中工具: source> generate toString時,如果繼承了另一個 POJO 類,注意在前面加一下 super.toString。
說明: 在方法執行抛出異常時,可以直接調用 POJO 的 toString()方法列印其屬性值,便于排查問題。
- 【推薦】使用索引通路用 String 的 split 方法得到的數組時,需做最後一個分隔符後有無内容的檢查,否則會有抛 IndexOutOfBoundsException 的風險。
1 2 3 4 5 說明: String str = "a,b,c,,"; String[] ary = str.split(","); //預期大于 3,結果是 3 System.out.println(ary.length); - 【推薦】當一個類有多個構造方法,或者多個同名方法,這些方法應該按順序放置在一起,便于閱讀。
- 【推薦】 類内方法定義順序依次是:公有方法或保護方法 > 私有方法 > getter/setter方法。
1 2 3 4 5 說明: 公有方法是類的調用者和維護者最關心的方法,首屏展示最好; 保護方法雖然隻是子類關心,也可能是“模闆設計模式”下的核心方法; 而私有方法外部一般不需要特别關心,是一個黑盒實作; 因為方法資訊價值較低,所有 Service 和 DAO 的 getter/setter 方法放在類體最後。 - 【推薦】 setter 方法中,參數名稱與類成員變量名稱一緻, this.成員名=參數名。在getter/setter 方法中,盡量不要增加業務邏輯,增加排查問題的難度。
1 2 3 4 5 6 7 8 反例: public Integer getData(){ if(true) { return data + 100; } else { return data - 100; } } - 【推薦】循環體内,字元串的聯接方式,使用 StringBuilder 的 append 方法進行擴充。
1 2 3 4 5 6 7 反例: String str = "start"; for(int i=0; i<100; i++){ `str = str + "hello"; } >說明: 反編譯出的位元組碼檔案顯示每次循環都會 new 出一個 StringBuilder 對象,然後進行append 操作,最後通過 toString 方法傳回 String 對象,造成記憶體資源浪費。 -
【推薦】 final 可提高程式響應效率,聲明成 final 的情況:
1) 不需要重新指派的變量,包括類屬性、局部變量。
2) 對象參數前加 final,表示不允許修改引用的指向。
3) 類方法确定不允許被重寫。
- 【推薦】慎用 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。
1 2 3 說明: 任何類、方法、參數、變量,嚴控通路範圍。過寬泛的通路範圍,不利于子產品解耦。 思考: 如果是一個 private 的方法,想删除就删除,可是一個 public 的 Service 方法,或者一個 public 的成員變量,删除一下,不得手心冒點汗嗎?變量像自己的小孩,盡量在自己的視線内,變量作用域太大,如果無限制的到處跑,那麼你會擔心的。
集合處理
-
【強制】 關于 hashCode 和 equals 的處理,遵循如下規則:
1) 隻要重寫 equals,就必須重寫 hashCode。
2) 因為 Set 存儲的是不重複的對象,依據 hashCode 和 equals 進行判斷,是以 Set 存儲的對象必須重寫這兩個方法。
3) 如果自定義對象做為 Map 的鍵,那麼必須重寫 hashCode 和 equals。
正例: String 重寫了 hashCode 和 equals 方法,是以我們可以非常愉快地使用 String 對象作為 key 來使用。
- 【強制】 ArrayList的subList結果不可強轉成ArrayList,否則會抛出 ClassCastException異常: java.util.RandomAccessSubList cannot be cast to java.util.ArrayList ;
說明: subList 傳回的是 ArrayList 的内部類 SubList,并不是 ArrayList ,而是ArrayList 的一個視圖,對于 SubList 子清單的所有操作最終會反映到原清單上。
- 【強制】 在 subList 場景中, 高度注意對原集合元素個數的修改,會導緻子清單的周遊、增加、删除均産生 ConcurrentModificationException 異常。
- 【強制】使用集合轉數組的方法,必須使用集合的 toArray(T[] array),傳入的是類型完全一樣的數組,大小就是 list.size()。
反例: 直接使用 toArray 無參方法存在問題,此方法傳回值隻能是 Object[]類,若強轉其它類型數組将出現 ClassCastException 錯誤。
1 2 3 4 5 6 7 8 9 正例: List<String> list = new ArrayList<String>(2); list.add("guan"); list.add("bao"); String[] array = new String[list.size()]; array = list.toArray(array); 說明: 使用 toArray 帶參方法,入參配置設定的數組空間不夠大時, toArray 方法内部将重新配置設定記憶體空間,并傳回新數組位址; 如果數組元素大于實際所需,下标為[ list.size() ]的數組 元素将被置為 null,其它數組元素保持原值,是以最好将方法入參數組大小定義與集合元素個數一緻。 - 【強制】使用工具類 Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方法,它的 add/remove/clear 方法會抛出 UnsupportedOperationException 異常。
1 2 3 4 5 6 說明: asList 的傳回對象是一個 Arrays 内部類,并沒有實作集合的修改方法。Arrays.asList展現的是擴充卡模式,隻是轉換接口,背景的資料仍是數組。 String[] str = new String[] { "a", "b" }; List list = Arrays.asList(str); 第一種情況: list.add("c"); 運作時異常。 第二種情況: str[0]= "gujin"; 那麼 list.get(0)也會随之修改 - 【強制】泛型通配符<? extends T>來接收傳回的資料,此寫法的泛型集合不能使用 add 方法。
說明: 蘋果裝箱後傳回一個<? extends Fruits>對象,此對象就不能往裡加任何水果,包括蘋果。
- 【強制】不要在 foreach 循環裡進行元素的 remove/add 操作。 remove 元素請使用 Iterator方式,如果并發操作,需要對 Iterator 對象加鎖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 反例: List<String> a = new ArrayList<String>(); a.add("1"); a.add("2"); for (String temp : a) { if("1".equals(temp)){ a.remove(temp); } } 說明: 以上代碼的執行結果肯定會出乎大家的意料,那麼試一下把“1”換成“2”,會是同樣的結果嗎? 正例: Iterator<String> it = a.iterator(); while(it.hasNext()){ String temp = it.next(); if(删除元素的條件){ it.remove(); } } - 【強制】 在 JDK7 版本以上, Comparator 要滿足自反性,傳遞性,對稱性,不然 Arrays.sort,Collections.sort 會報 IllegalArgumentException 異常。
1 2 3 4 5 6 7 8 9 10 11 12 說明: 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; } } - 【推薦】集合初始化時,盡量指定集合初始值大小。
說明: ArrayList 盡量使用 ArrayList(int initialCapacity) 初始化。
- 【推薦】使用 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 方法進行周遊、對比、 去重操作。
- 【推薦】高度注意 Map 類集合 K/V 能不能存儲 null 值的情況,如下表格:
集合類 | Key | Value | Super | 說明 |
---|---|---|---|---|
Hashtable | 不允許為 null | 不允許為 null | Dictionary | 線程安全 |
ConcurrentHashMap | 不允許為 null | 不允許為 null | AbstractMap | 分段鎖技術 |
TreeMap | 不允許為 null | 允許為 null | AbstractMap | 線程不安全 |
HashMap | 允許為 null | 允許為 null | AbstractMap | 線程不安全 |
反例: 由于 HashMap 的幹擾,很多人認為 ConcurrentHashMap 是可以置入 null 值,注意存儲null 值時會抛出 NPE 異常。
并發處理
- 【強制】 擷取單例對象需要保證線程安全,其中的方法也要保證線程安全。
說明: 資源驅動類、工具類、單例工廠類都需要注意。
- 【強制】建立線程或線程池時請指定有意義的線程名稱,友善出錯時回溯。
1 2 3 4 5 正例: public class TimerTaskThread extends Thread { public TimerTaskThread(){ super.setName("TimerTaskThread"); ... } - 【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式建立線程。
說明: 使用線程池的好處是減少在建立和銷毀線程上所花的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統建立大量同類線程而導緻消耗完記憶體或者“過度切換”的問題。
- 【強制】線程池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明确線程池的運作規則,規避資源耗盡的風險。
1 2 3 4 5 6 說明: Executors 傳回的線程池對象的弊端如下: 1) FixedThreadPool 和 SingleThreadPool: 允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,進而導緻 OOM。 2) CachedThreadPool 和 ScheduledThreadPool: 允許的建立線程數量為 Integer.MAX_VALUE, 可能會建立大量的線程,進而導緻 OOM。 - 【強制】 SimpleDateFormat 是線程不安全的類,一般不要定義為 static 變量,如果定義為static,必須加鎖,或者使用 DateUtils 工具類。
1 2 3 4 5 6 7 8 9 10 正例: 注意線程安全,使用 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代替Simpledateformatter, 官方給出的解釋:simple beautiful strong immutable thread-safe。 - 【強制】高并發時,同步調用應該去考量鎖的性能損耗。能用無鎖資料結構,就不要用鎖; 能鎖區塊,就不要鎖整個方法體; 能用對象鎖,就不要用類鎖。
- 【強制】對多個資源、資料庫表、對象同時加鎖時,需要保持一緻的加鎖順序,否則可能會造成死鎖。
說明: 線程一需要對表 A、 B、 C 依次全部加鎖後才可以進行更新操作,那麼線程二的加鎖順序也必須是 A、 B、 C,否則可能出現死鎖。
- 【強制】并發修改同一記錄時,避免更新丢失,要麼在應用層加鎖,要麼在緩存加鎖,要麼在資料庫層使用樂觀鎖,使用 version 作為更新依據。
說明: 如果每次通路沖突機率小于 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數不得小于 3 次。
- 【強制】多線程并行處理定時任務時, Timer 運作多個 TimeTask 時,隻要其中之一沒有捕獲抛出的異常,其它任務便會自動終止運作,使用 ScheduledExecutorService 則沒有這個問題。
- 【推薦】使用 CountDownLatch 進行異步轉同步操作,每個線程退出前必須調用 countDown方法,線程執行代碼注意 catch 異常,確定 countDown 方法可以執行,避免主線程無法執行至 countDown 方法,直到逾時才傳回結果。
說明: 注意,子線程抛出異常堆棧,不能在主線程 try-catch 到。
- 【推薦】避免 Random 執行個體被多線程使用,雖然共享該執行個體是線程安全的,但會因競争同一seed 導緻的性能下降。
說明: Random 執行個體包括 java.util.Random 的執行個體或者 Math.random()執行個體。
正例: 在 JDK7 之後,可以直接使用 API ThreadLocalRandom,在 JDK7 之前,可以做到每個線程一個執行個體。
- 【推薦】通過雙重檢查鎖( double-checked locking)( 在并發場景) 實作延遲初始化的優化問題隐患(可參考 The “Double-Checked Locking is Broken” Declaration),推薦問題解決方案中較為簡單一種( 适用于 JDK5 及以上版本) ,将目标屬性聲明為 volatile 型。
1 2 3 4 5 6 7 8 9 10 11 12 13 反例: class Foo { private Helper helper = null; public Helper getHelper() { if (helper == null) synchronized(this) { if (helper == null) helper = new Helper(); } return helper; } // other functions and members... } -
【參考】 volatile 解決多線程記憶體不可見問題。對于一寫多讀,是可以解決變量同步問題,但是如果多寫,同樣無法解決線程安全問題。
如果是 count++操作,使用如下類實作:
AtomicInteger count = new AtomicInteger();
count.addAndGet(1);
如果是 JDK8,推薦使用 LongAdder 對象,比 AtomicLong 性能更好( 減少樂觀鎖的重試次數) 。
- 【參考】 HashMap 在容量不夠進行 resize 時由于高并發可能出現死鍊,導緻 CPU 飙升,在開發過程中注意規避此風險。
- 【參考】 ThreadLocal 無法解決共享對象的更新問題, ThreadLocal 對象建議使用 static修飾。這個變量是針對一個線程内所有操作共有的,是以設定為靜态變量,所有此類執行個體共享此靜态變量 ,也就是說在類第一次被使用時裝載,隻配置設定一塊存儲空間,所有此類的對象(隻要是這個線程内定義的)都可以操控這個變量。
控制語句
- 【強制】在一個 switch 塊内,每個 case 要麼通過 break/return 等來終止,要麼注釋說明程式将繼續執行到哪一個 case 為止; 在一個 switch 塊内,都必須包含一個 default 語句并且放在最後,即使它什麼代碼也沒有。
- 【強制】在 if/else/for/while/do 語句中必須使用大括号,即使隻有一行代碼,避免使用下面的形式: if (condition) statements;
-
【推薦】推薦盡量少用 else, if-else 的方式可以改寫成:
if(condition){
…
return obj;
}
// 接着寫 else 的業務邏輯代碼;
1 2 3 4 說明: 如果非得使用 if()...else if()...else...方式表達邏輯,【強制】請勿超過 3 層,超過請使用狀态設計模式。 正例: 邏輯上超過 3 層的 if-else 代碼可以使用衛語句,或者狀态模式來實作。 -
【推薦】除常用方法(如 getXxx/isXxx)等外,不要在條件判斷中執行其它複雜的語句,将複雜邏輯判斷的結果指派給一個有意義的布爾變量名,以提高可讀性。
說明:
很多 if 語句内的邏輯相當複雜,閱讀者需要分析條件表達式的最終結果,才能明确什麼樣的條件執行什麼樣的語句,那麼,如果閱讀者分析邏輯表達式錯誤呢?
1 2 3 4 5 6 7 8 9 10 11 正例: //僞代碼如下 boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed) { ... } ------------- 反例: if ((file.open(fileName, "w") != null) && (...) || (...)) { ... } - 【推薦】循環體中的語句要考量性能,以下操作盡量移至循環體外處理,如定義對象、變量、擷取資料庫連接配接,進行不必要的 try-catch 操作( 這個 try-catch 是否可以移至循環體外) 。
- 【推薦】接口入參保護,這種場景常見的是用于做批量操作的接口。
-
【參考】方法中需要進行參數校驗的場景:
1) 調用頻次低的方法。
2) 執行時間開銷很大的方法,參數校驗時間幾乎可以忽略不計,但如果因為參數錯誤導緻中間執行回退,或者錯誤,那得不償失。
3) 需要極高穩定性和可用性的方法。
4) 對外提供的開放接口,不管是 RPC/API/HTTP 接口。
5) 敏感權限入口。
-
【 參考】方法中不需要參數校驗的場景:
1) 極有可能被循環調用的方法,不建議對參數進行校驗。但在方法說明裡必須注明外部參數檢查。
2) 底層的方法調用頻度都比較高,一般不校驗。畢竟是像純淨水過濾的最後一道,參數錯誤不太可能到底層才會暴露問題。一般 DAO 層與 Service 層都在同一個應用中,部署在同一 台伺服器中,是以 DAO 的參數校驗,可以省略。
3) 被聲明成 private 隻會被自己代碼所調用的方法,如果能夠确定調用方法的代碼傳入參數已經做過檢查或者肯定不會有問題,此時可以不校驗參數。
注釋規約
- 【強制】 類、類屬性、類方法的注釋必須使用 Javadoc 規範,使用/内容/格式,不得使用//xxx 方式。
說明: 在 IDE 編輯視窗中, Javadoc 方式會提示相關注釋,生成 Javadoc 可以正确輸出相應注釋; 在 IDE 中,工程調用方法時,不進入方法即可懸浮提示方法、參數、傳回值的意義,提高 閱讀效率。
- 【強制】所有的抽象方法( 包括接口中的方法) 必須要用 Javadoc 注釋、除了傳回值、參數、異常說明外,還必須指出該方法做什麼事情,實作什麼功能。
說明: 對子類的實作要求,或者調用注意事項,請一并說明。
- 【強制】所有的類都必須添加建立者資訊。
- 【強制】方法内部單行注釋,在被注釋語句上方另起一行,使用//注釋。方法内部多行注釋使用/ \/ 注釋,注意與代碼對齊。
- 【強制】所有的枚舉類型字段必須要有注釋,說明每個資料項的用途。
- 【推薦】與其“半吊子”英文來注釋,不如用中文注釋把問題說清楚。專有名詞與關鍵字保持英文原文即可。
反例: “TCP 連接配接逾時”解釋成“傳輸控制協定連接配接逾時”,了解反而費腦筋。
- 【推薦】代碼修改的同時,注釋也要進行相應的修改,尤其是參數、傳回值、異常、核心邏輯等的修改。
說明: 代碼與注釋更新不同步,就像路網與導航軟體更新不同步一樣,如果導航軟體嚴重滞後,就失去了導航的意義。
- 【參考】注釋掉的代碼盡量要配合說明,而不是簡單的注釋掉。
1 2 3 說明: 代碼被注釋掉有兩種可能性: 1)後續會恢複此段代碼邏輯。 2)永久不用。前者如果沒有備注資訊,難以知曉注釋動機。後者建議直接删掉( 代碼倉庫儲存了曆史代碼)。 - 【參考】對于注釋的要求:
1 2 3 第一、能夠準确反應設計思想和代碼邏輯; 第二、能夠描述業務含義,使别的程式員能夠迅速了解到代碼背後的資訊。完全沒有注釋的大段代碼對于閱讀者形同天書,注釋是給自己看的,即使隔很長時間,也能清晰了解當時的思路; 注釋也是給繼任者看的,使其能夠快速接替自己的工作。 - 【參考】好的命名、代碼結構是自解釋的,注釋力求精簡準确、表達到位。避免出現注釋的一個極端:過多過濫的注釋,代碼的邏輯一旦修改,修改注釋是相當大的負擔。
1 2 3 4 反例: // put elephant into fridge put(elephant, fridge); 方法名 put,加上兩個有意義的變量名 elephant 和 fridge,已經說明了這是在幹什麼,語義清晰的代碼不需要額外的注釋。 - 【參考】特殊注釋标記,請注明标記人與标記時間。注意及時處理這些标記,通過标記掃描,經常清理此類标記。線上故障有時候就是來源于這些标記處的代碼。
1 2 3 1) 待辦事宜( TODO) :(标記人,标記時間, [預計處理時間]) 表示需要實作,但目前還未實作的功能。這實際上是一個 Javadoc 的标簽,目前的 Javadoc還沒有實作,但已經被廣泛使用。隻能應用于類,接口和方法( 因為它是一個 Javadoc 标簽) 。 2) 錯誤,不能工作( FIXME) :( 标記人,标記時間, [預計處理時間])在注釋中用 FIXME 标記某代碼是錯誤的,而且不能工作,需要及時糾正的情況
其它
- 【強制】在使用正規表達式時,利用好其預編譯功能,可以有效加快正則比對速度。
說明: 不要在方法體内定義: Pattern pattern = Pattern.compile(規則);
- 【強制】 velocity 調用 POJO 類的屬性時,建議直接使用屬性名取值即可,模闆引擎會自動按規範調用 POJO 的 getXxx(),如果是 boolean 基本資料類型變量( boolean 命名不需要加 is字首) ,會自動調用 isXxx()方法。
說明: 注意如果是 Boolean 包裝類對象,優先調用 getXxx()的方法。
- 【強制】背景輸送給頁面的變量必須加$!{var}——中間的感歎号。
說明: 如果 var=null 或者不存在,那麼${var}會直接顯示在頁面上。
- 【強制】注意 Math.random() 這個方法傳回是 double 類型,注意取值的範圍 0≤x<1( 能夠取到零值,注意除零異常) ,如果想擷取整數類型的随機數,不要将 x 放大 10 的若幹倍然後取整,直接使用 Random 對象的 nextInt 或者 nextLong 方法
- 【強制】擷取目前毫秒數 System.currentTimeMillis(); 而不是 new Date().getTime();
說明: 如果想擷取更加精确的納秒級時間值,用 System.nanoTime()。在 JDK8 中,針對統計時間等場景,推薦使用 Instant 類。
- 【推薦】盡量不要在 vm 中加入變量聲明、邏輯運算符,更不要在 vm 模闆中加入任何複雜的邏輯。
- 【推薦】 任何資料結構的構造或初始化,都應指定大小,避免資料結構無限增長吃光記憶體。
- 【推薦】對于“明确停止使用的代碼和配置”,如方法、變量、類、配置檔案、動态配置屬性等要堅決從程式中清理出去,避免造成過多垃圾。
異常日志
異常處理
-
【強制】不要捕獲 Java 類庫中定義的繼承自 RuntimeException 的運作時異常類,如:
IndexOutOfBoundsException / NullPointerException,這類異常由程式員預檢查來規避,保證程式健壯性。
正例: if(obj != null) {...}
反例: try { obj.method() } catch(NullPointerException e){...}
- 【強制】異常不要用來做流程控制,條件控制,因為異常的處理效率比條件分支低。
- 【強制】對大段代碼進行 try-catch,這是不負責任的表現。 catch 時請厘清穩定代碼和非穩定代碼,穩定代碼指的是無論如何不會出錯的代碼。對于非穩定代碼的 catch 盡可能進行區分異常類型,再做對應的異常處理。
- 【強制】捕獲異常是為了處理它,不要捕獲了卻什麼都不處理而抛棄之,如果不想處理它,請将該異常抛給它的調用者。最外層的業務使用者,必須處理異常,将其轉化為使用者可以了解的内容。
- 【強制】有 try 塊放到了事務代碼中, catch 異常後,如果需要復原事務,一定要注意手動復原事務。
- 【強制】 finally 塊必須對資源對象、流對象進行關閉,有異常也要做 try-catch。
說明: 如果 JDK7,可以使用 try-with-resources 方式。
- 【強制】不能在 finally 塊中使用 return, finally 塊中的 return 傳回後方法結束執行,不會再執行 try 塊中的 return 語句。
- 【強制】捕獲異常與抛異常,必須是完全比對,或者捕獲異常是抛異常的父類。
說明: 如果預期對方抛的是繡球,實際接到的是鉛球,就會産生意外情況。
- 【推薦】方法的傳回值可以為 null,不強制傳回空集合,或者空對象等,必須添加注釋充分說明什麼情況下會傳回 null 值。調用方需要進行 null 判斷防止 NPE 問題。
說明: 本規約明确防止 NPE 是調用者的責任。即使被調用方法傳回空集合或者空對象,對調用者來說,也并非高枕無憂,必須考慮到遠端調用失敗,運作時異常等場景傳回 null 的情況。
- 【推薦】防止 NPE,是程式員的基本修養,注意 NPE 産生的場景:
1 2 3 4 5 6 7 1) 傳回類型為包裝資料類型,有可能是 null,傳回 int 值時注意判空。 反例: public int f(){ return Integer 對象}; 如果為 null,自動解箱抛 NPE。 2) 資料庫的查詢結果可能為 null。 3) 集合裡的元素即使 isNotEmpty,取出的資料元素也可能為 null 4) 遠端調用傳回對象,一律要求進行 NPE 判斷。 5) 對于 Session 中擷取的資料,建議 NPE 檢查,避免空指針。 6) 級聯調用 obj.getA().getB().getC(); 一連串調用,易産生 NPE。 - 【推薦】在代碼中使用“抛異常”還是“傳回錯誤碼”,對于公司外的 http/api 開放接口必須使用“錯誤碼”; 而應用内部推薦異常抛出; 跨應用間 RPC 調用優先考慮使用 Result 方式,封裝 isSuccess、 “錯誤碼”、 “錯誤簡短資訊”。
1 2 3 4 5 說明: 關于 RPC 方法傳回方式使用 Result 方式的理由: 1) 使用抛異常傳回方式,調用方如果沒有捕獲到就會産生運作時錯誤。 2) 如果不加棧資訊,隻是 new 自定義異常,加入自己的了解的 error message,對于調用 端解決問題的幫助不會太多。如果加了棧資訊,在頻繁調用出錯的情況下,資料序列化和傳輸 的性能損耗也是問題。 - 【推薦】定義時區分 unchecked / checked 異常,避免直接使用 RuntimeException 抛出,更不允許抛出 Exception 或者 Throwable,應使用有業務含義的自定義異常。推薦業界已定義過的自定義異常,如: DAOException / ServiceException 等。
- 【參考】避免出現重複的代碼( Don’t Repeat Yourself) ,即 DRY 原則。
說明: 随意複制和粘貼代碼,必然會導緻代碼的重複,在以後需要修改時,需要修改所有的副本,容易遺漏。必要時抽取共性方法,或者抽象公共類,甚至是共用子產品。
正例: 一個類中有多個 public 方法,都需要進行數行相同的參數校驗操作,這個時候請抽取: private boolean checkParam(DTO dto){...}
日志規約
- 【強制】應用中不可直接使用日志系統( Log4j、 Logback) 中的 API,而應依賴使用日志架構SLF4J 中的 API,使用門面模式的日志架構,有利于維護和各個類的日志處理方式統一。
1 2 3 import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(Abc.class); - 【強制】日志檔案推薦至少儲存 15 天,因為有些異常具備以“周”為頻次發生的特點。
- 【強制】應用中的擴充日志( 如打點、臨時監控、通路日志等) 命名方式:
1 2 3 4 5 6 appName_logType_logName.log。 logType:日志類型,推薦分類有 stats/desc/monitor/visit 等; logName:日志描述。 這種命名的好處:通過檔案名就可知道日志檔案屬于什麼應用,什麼類型,什麼目的,也有利于歸類查找。 正例: mppserver 應用中單獨監控時區轉換異常,如:mppserver_monitor_timeZoneConvert.log 說明: 推薦對日志進行分類,錯誤日志和業務日志盡量分開存放,便于開發人員檢視,也便于通過日志對系統進行及時監控 - 【強制】對 trace/debug/info 級别的日志輸出,必須使用條件輸出形式或者使用占位符的方式。
1 2 3 4 5 6 7 8 9 說明: logger.debug("Processing trade with id: " + id + " symbol: " + symbol); 如果日志級别是 warn,上述日志不會列印,但是會執行字元串拼接操作,如果 symbol 是對象,會執行 toString()方法,浪費了系統資源,執行了上述操作,最終日志卻沒有列印。 正例: ( 條件) if (logger.isDebugEnabled()) { logger.debug("Processing trade with id: " + id + " symbol: " + symbol); } 正例: ( 占位符) logger.debug("Processing trade with id: {} symbol : {} ", id, symbol); - 【強制】避免重複列印日志,浪費磁盤空間,務必在 log4j.xml 中設定 additivity=false。
正例: <logger name="com.taobao.dubbo.config" additivity="false">
- 【強制】異常資訊應該包括兩類資訊:案發現場資訊和異常堆棧資訊。如果不處理,那麼往上抛。
正例: logger.error(各類參數或者對象 toString + "\_" + e.getMessage(), e);
- 【推薦】可以使用 warn 日志級别來記錄使用者輸入參數錯誤的情況,避免使用者投訴時,無所适從。注意日志輸出的級别, error 級别隻記錄系統邏輯出錯、異常等重要的錯誤資訊。如非必要,請不要在此場景打出 error 級别。
- 【推薦】謹慎地記錄日志。生産環境禁止輸出 debug 日志; 有選擇地輸出 info 日志; 如果使用 warn 來記錄剛上線時的業務行為資訊,一定要注意日志輸出量的問題,避免把伺服器磁盤撐爆,并記得及時删除這些觀察日志。
1 2 3 說明: 大量地輸出無效日志,不利于系統性能提升,也不利于快速定位錯誤點。 記錄日志時請思考: 這些日志真的有人看嗎?看到這條日志你能做什麼?能不能給問題排查帶來好處?
二方庫規約
- 【強制】定義 GAV 遵從以下規則:
1 2 3 4 5 6 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 2 3 4 1) 主版本号:當做了不相容的 API 修改,或者增加了能改變産品方向的新功能。 2) 次版本号:當做了向下相容的功能性新增( 新增類、接口等) 。 3) 修訂号:修複 bug,沒有修改方法簽名的功能加強,保持 API 相容性。 說明: 起始版本号必須為: 1.0.0,而不是 0.0.1 -
【強制】線上應用不要依賴 SNAPSHOT 版本( 安全包除外); 正式釋出的類庫必須使用 RELEASE版本号更新+1 的方式,且版本号不允許覆寫更新,必須去中央倉庫進行查證。
說明: 不依賴 SNAPSHOT 版本是保證應用釋出的幂等性。另外,也可以加快編譯時的打包建構。
- 【強制】二方庫的新增或更新,保持除功能點之外的其它 jar 包仲裁結果不變。如果有改變,必須明确評估和驗證, 建議進行 dependency:resolve 前後資訊比對,如果仲裁結果完全不一緻,那麼通過 dependency:tree 指令,找出差異點,進行排除 jar 包。
- 【強制】二方庫裡可以定義枚舉類型,參數可以使用枚舉類型,但是接口傳回值不允許使用枚舉類型或者包含枚舉類型的 POJO 對象。
- 【強制】依賴于一個二方庫群時,必須定義一個統一版本變量, 避免版本号不一緻。
說明: 依賴 springframework-core,-context,-beans,它們都是同一個版本,可以定義一個變量來儲存版本: ${spring.version},定義依賴的時候,引用該版本。
- 【強制】禁止在子項目的 pom 依賴中出現相同的 GroupId,相同的 ArtifactId,但是不同的Version。
說明: 在本地調試時會使用各子項目指定的版本号,但是合并成一個 war,隻能有一個版本号出現在最後的 lib 目錄中。曾經出現過線下調試是正确的,釋出到線上出故障的先例。
- 【推薦】所有 pom 檔案中的依賴聲明放在語句塊中,所有版本仲裁放在語句塊中。
說明: <dependencyManagement>裡隻是聲明版本,并不實作引入,是以子項目需要顯式的聲明依賴, version 和 scope 都讀取自父 pom。而<dependencies>所有聲明在主 pom 的<dependencies>裡的依賴都會自動引入,并預設被所有的子項目繼承。
- 【推薦】二方庫盡量不要有配置項,最低限度不要再增加配置項。
- 【參考】為避免應用二方庫的依賴沖突問題,二方庫釋出者應當遵循以下原則:
1 2 1) 精簡可控原則。移除一切不必要的 API 和依賴,隻包含 Service API、必要的領域模型對 象、 Utils 類、常量、枚舉等。如果依賴其它二方庫,盡量是 provided 引入,讓二方庫使用者去依賴具體版本号; 無 log 具體實作,隻依賴日志架構。 2) 穩定可追溯原則。每個版本的變化應該被記錄,二方庫由誰維護,源碼在哪裡,都需要能友善查到。除非使用者主動更新版本,否則公共二方庫的行為不應該發生變化。
# java , guide