一、編程規約
(一) 命名規約
1. 【強制】所有編程相關命名均不能以下划線或美元符號開始,也不能以下划線或美元符號結束。
反例: _name / __name / $Object / name_ / name$ / Object$
凡是以兩個或一個下划線開始,后面緊跟着一個大寫字母的標識符,不管它出現在哪里,都是保留給編譯程序或標准庫函數使用的。 此外,凡是以一個下划線開始,后面不管跟着什么內容的標識符,如果它出現在文件范圍內(即它不是出現在一個函數內),那么它也是被保留的。 如果你用一個保留的標識符來作一個變量的名稱,結果是沒有定義的(程序可能無法編譯,或者可以編譯但會崩潰)。 即使你能非常幸運地找到一個目前還沒有被你的編譯程序或函數庫使用的標識符,你也應該記住這樣的標識符是保留起來供將來使用的。 因此,最好還是避免使用以下划線開始的變量名或函數名。
2. 【強制】所有編程相關的命名嚴禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。
說明:正確的英文拼寫和語法可以讓閱讀者易於理解,避免歧義。注意,即使純拼音命名方式也要避免采用。
反例: DaZhePromotion [打折] / getPingfenByName() [評分] / int 變量 = 3;
正例: ali / alibaba / taobao / cainiao / aliyun / youku / hangzhou 等國際通用的名稱,可視為英文。
3. 【強制】類名使用 UpperCamelCase風格,必須遵從駝峰形式,但以下情形例外:(領域模型的相關命名)DO / DTO / VO / DAO等。
正例: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. 【強制】中括號是數組類型的一部分,數組定義如下:String[] args;
反例:請勿使用 String args[]的方式來定義
8. 【強制】POJO類中的任何布爾類型的變量,都不要加 is,否則部分框架解析會引起序列化錯誤。
反例:定義為基本數據類型 boolean isSuccess;的屬性,它的方法也是 isSuccess(),RPC框架在反向解析的時候,“以為”對應的屬性名稱是 success,導致屬性獲取不到,進而拋出異常。
9. 【強制】包名統一使用小寫,點分隔符之間有且僅有一個自然語義的英語單詞。包名統一使用單數形式,但是類名如果有復數含義,類名可以使用復數形式。
正例: 應用工具類包名為 com.alibaba.mpp.util、類名為 MessageUtils(此規則參考 spring的框架結構)
10.【強制】杜絕完全不規范的縮寫,避免望文不知義。
反例:<某業務代碼>AbstractClass“縮寫”命名成 AbsClass;condition“縮寫”命名成condi,此類隨意縮寫嚴重降低了代碼的可閱讀性。
11.【推薦】如果使用到了設計模式,建議在類名中體現出具體模式。
說明:將設計模式體現在名字中,有利於閱讀者快速理解架構設計思想。
正例:public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;
12.【推薦】接口類中的方法和屬性不要加任何修飾符號(public 也不要加),保持代碼的簡潔性,並加上有效的 javadoc注釋。盡量不要在接口里定義變量,如果一定要定義變量,肯定是與接口方法相關,並且是整個應用的基礎常量。
正例:接口方法簽名:void f();
接口基礎常量表示:String COMPANY = "alibaba";
反例:接口方法定義:public abstract void f();
說明:JDK8中接口允許有默認實現,那么這個 default方法,是對所有實現類都有價值的默認實現。
13.接口和實現類的命名有兩套規則:
1)【強制】對於 Service和 DAO類,基於 SOA的理念,暴露出來的服務一定是接口,內部的實現類用 Impl的后綴與接口區別。
正例:CacheServiceImpl實現 CacheService接口。
2)【推薦】 如果是形容能力的接口名稱,取對應的形容詞做接口名(通常是–able的形式)。
正例:AbstractTranslator實現 Translatable。
14.【參考】枚舉類名建議帶上 Enum后綴,枚舉成員名稱需要全大寫,單詞間用下划線隔開。
說明:枚舉其實就是特殊的常量類,且構造方法被默認強制是私有。
正例:枚舉名字:DealStatusEnum;成員名稱:SUCCESS / UNKOWN_REASON。
15.【參考】各層命名規約:
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. 【強制】不允許出現任何魔法值(即未經定義的常量)直接出現在代碼中。
反例: String key="Id#taobao_"+tradeId;
cache.put(key, value);
2. 【強制】long或者 Long初始賦值時,必須使用大寫的 L,不能是小寫的 l,小寫容易跟數字 1混淆,造成誤解。
說明:Long a = 2l; 寫的是數字的 21,還是 Long型的 2?
3. 【推薦】不要使用一個常量類維護所有常量,應該按常量功能進行歸類,分開維護。如:緩存相關的常量放在類:CacheConsts下;系統配置相關的常量放在類:ConfigConsts下。
說明:大而全的常量類,非得 ctrl+f才定位到修改的常量,不利於理解,也不利於維護。
4. 【推薦】常量的復用層次有五層:跨應用共享常量、應用內共享常量、子工程內共享常量、包內共享常量、類內共享常量。
1) 跨應用共享常量:放置在二方庫中,通常是 client.jar中的 const目錄下。
2) 應用內共享常量:放置在一方庫的 modules中的 const目錄下。
反例:易懂變量也要統一定義成應用內共享常量,兩位攻城師在兩個類中分別定義了表示“是”的變量:
類 A中:public static final String YES = "yes";
類 B中:public static final String YES = "y";
A.YES.equals(B.YES),預期是 true,但實際返回為 false,導致產生線上問題。
3) 子工程內部共享常量:即在當前子工程的 const目錄下。
4) 包內共享常量:即在當前包下單獨的 const目錄下。
5) 類內共享常量:直接在類內部 private static final定義。
5. 【推薦】如果變量值僅在一個范圍內變化用 Enum類。如果還帶有名稱之外的延伸屬性,必須使用 Enum類,下面正例中的數字就是延伸信息,表示星期幾。
正例:public Enum{ MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5),SATURDAY(6), SUNDAY(7);}
(三) 格式規約
1. 【強制】大括號的使用約定。如果是大括號內為空,則簡潔地寫成{}即可,不需要換行;如果是非空代碼塊則:
1) 左大括號前不換行。
2) 左大括號后換行。
3) 右大括號前換行。
4) 右大括號后還有 else等代碼則不換行;表示終止右大括號后必須換行。
2. 【強制】 左括號和后一個字符之間不出現空格;同樣,右括號和前一個字符之間也不出現空格。詳見第 5條下方正例提示。
3. 【強制】if/for/while/switch/do等保留字與左右括號之間都必須加空格。
4. 【強制】任何運算符左右必須加一個空格。
說明:運算符包括賦值運算符=、邏輯運算符&&、加減乘除符號、三目運行符等。
5. 【強制】代碼塊縮進 4個空格,如果使用 tab縮進,請設置成 1個 tab為 4個空格。
正例: (涉及 1-5點)
public static void main(String args[]) { // 縮進 4個空格 String say = "hello"; // 運算符的左右必須有一個空格 int flag = 0; // 關鍵詞 if與括號之間必須有一個空格,括號內 f與左括號,1與右括號不需要空格 if (flag == 0) { System.out.println(say); } // 左大括號前加空格且不換行;左大括號后換行 if (flag == 1) { System.out.println("world"); // 右大括號前換行,右大括號后有 else,不用換行 } else { System.out.println("ok"); // 右大括號做為結束,必須換行 } }
6. 【強制】單行字符數限制不超過 120個,超出需要換行,換行時,遵循如下原則:
1) 換行時相對上一行縮進 4個空格。
2) 運算符與下文一起換行。
3) 方法調用的點符號與下文一起換行。
4) 在多個參數超長,逗號后進行換行。
5) 在括號前不要換行,見反例。
正例:
StringBuffer sb = new StringBuffer(); //超過 120個字符的情況下,換行縮進 4個空格,並且方法前的點符號一起換行 sb.append("zi").append("xin")… .append("huang");
反例:
StringBuffer sb = new StringBuffer(); //超過 120個字符的情況下,不要在括號前換行 sb.append("zi").append("xin")…append
("huang"); //參數很多的方法調用也超過 120個字符,逗號后才是換行處 method(args1, args2, args3, ...
, argsX);
7. 【強制】方法參數在定義和傳入時,多個參數逗號后邊必須加空格。
正例:下例中實參的"a",后邊必須要有一個空格。
method("a", "b", "c");
8. 【推薦】沒有必要增加若干空格來使某一行的字符與上一行的相應字符對齊。
正例:
int a = 3;
long b = 4L;
float c = 5F;
StringBuffer sb = new StringBuffer();
說明:增加 sb這個變量,如果需要對齊,則給 a、b、c都要增加幾個空格,在變量比較多的情況下,是一種累贅的事情。
9. 【強制】IDE的 text file encoding設置為 UTF-8; IDE中文件的換行符使用 Unix格式,不要使用 windows格式。
CR : Carriage Return 回車 LF: linefeed 換行
<換行>即\n(LF) <回車><換行>\r\n (CR\LF) \r 十進制ASCII代碼是13, 十六進制代碼為0x0d \n 十進制ASCII代碼是10, 十六制為0x0a windows中的換行符是\r\n 先回車再換行 linux/unix下的換行符是\n
推薦博客:
http://www.cnblogs.com/dartagnan/archive/2010/12/14/2003499.html
http://www.cnblogs.com/DreamDrive/p/6887926.html
10.【推薦】方法體內的執行語句組、變量的定義語句組、不同的業務邏輯之間或者不同的語義之間插入一個空行。相同業務邏輯和語義之間不需要插入空行。
說明:沒有必要插入多行空格進行隔開。
(四) OOP規約
1. 【強制】避免通過一個類的對象引用訪問此類的靜態變量或靜態方法,無謂增加編譯器解析成本,直接用類名來訪問即可。
2. 【強制】所有的覆寫方法,必須加@Override注解。
反例:getObject()與 get0bject()的問題。一個是字母的 O,一個是數字的 0,加@Override可以准確判斷是否覆蓋成功。另外,如果在抽象類中對方法簽名進行修改,其實現類會馬上編譯報錯。
3. 【強制】相同參數類型,相同業務含義,才可以使用 Java的可變參數,避免使用 Object。
說明:可變參數必須放置在參數列表的最后。(提倡同學們盡量不用可變參數編程)
正例:public User getUsers(String type, Integer... 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方法比較。
說明:對於 Integervar=?在-128至 127之間的賦值,Integer對象是在 IntegerCache.cache產生,會復用已有對象,這個區間內的 Integer值可以直接使用==進行判斷,但是這個區間之外的所有數據,都會在堆上產生,並不會復用已有對象,這是一個大坑,推薦使用 equals方法進行判斷。
class A{ public static void main(String[] args) { Integer a = 128, b = 128; System.out.println(a == b);//返回false Integer c = 127, d = 127; System.out.println(c == d);//返回true } }
推薦博客:
http://www.open-open.com/lib/view/open1482374807208.html
8. 【強制】關於基本數據類型與包裝數據類型的使用標准如下:
1) 所有的 POJO類屬性必須使用包裝數據類型。
2) RPC方法的返回值和參數必須使用包裝數據類型。
3) 所有的局部變量推薦使用基本數據類型。
說明:POJO類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何NPE問題,或者入庫檢查,都由使用者來保證。
正例:數據庫的查詢結果可能是 null,因為自動拆箱,用基本數據類型接收有 NPE風險。
反例:某業務的交易報表上顯示成交總額漲跌情況,即正負 x%,x為基本數據類型,調用的RPC服務,調用不成功時,返回的是默認值,頁面顯示:0%,這是不合理的,應該顯示成中划
線-。所以包裝數據類型的 null值,能夠表示額外的信息,如:遠程調用失敗,異常退出。
9. 【強制】定義 DO/DTO/VO等 POJO類時,不要設定任何屬性默認值。
反例:某業務的 DO的 gmtCreate默認值為 new Date();但是這個屬性在數據提取時並沒有置入具體值,在更新其它字段時又附帶更新了此字段,導致創建時間被修改成當前時間。
10.【強制】序列化類新增屬性時,請不要修改 serialVersionUID字段,避免反序列失敗;如果完全不兼容升級,避免反序列化混亂,那么請修改 serialVersionUID值。
說明:注意 serialVersionUID不一致會拋出序列化運行時異常。
11.【強制】構造方法里面禁止加入任何業務邏輯,如果有初始化邏輯,請放在 init方法中。
12.【強制】POJO類必須寫 toString方法。使用工具類 source> generate toString時,如果繼承了另一個 POJO類,注意在前面加一下 super.toString。
說明:在方法執行拋出異常時,可以直接調用 POJO的 toString()方法打印其屬性值,便於排查問題。
13.【推薦】使用索引訪問用 String的 split方法得到的數組時,需做最后一個分隔符后有無內容的檢查,否則會有拋 IndexOutOfBoundsException的風險。
說明:
String str = "a,b,c,,"; String[] ary = str.split(","); //預期大於 3,結果是 3 System.out.println(ary.length);
14.【推薦】當一個類有多個構造方法,或者多個同名方法,這些方法應該按順序放置在一起,便於閱讀。
15.【推薦】 類內方法定義順序依次是:公有方法或保護方法 > 私有方法 > getter/setter方法。
說明:公有方法是類的調用者和維護者最關心的方法,首屏展示最好;保護方法雖然只是子類關心,也可能是“模板設計模式”下的核心方法;而私有方法外部一般不需要特別關心,是一個黑盒實現;因為方法信息價值較低,所有 Service和 DAO的 getter/setter方法放在類體最后。
16.【推薦】setter方法中,參數名稱與類成員變量名稱一致,this.成員名=參數名。在getter/setter方法中,盡量不要增加業務邏輯,增加排查問題難度。
反例:
public Integer getData(){ if(true) { return data + 100; } else { return data - 100; } }
17.【推薦】循環體內,字符串的聯接方式,使用 StringBuilder的 append方法進行擴展。
反例:
String str = "start"; for(int i=0; i<100; i++){ str = str + "hello"; }
說明:反編譯出的字節碼文件顯示每次循環都會 new出一個 StringBuilder對象,然后進行append操作,最后通過 toString方法返回 String對象,造成內存資源浪費。
18.【推薦】final可提高程序響應效率,聲明成 final的情況:
1) 不需要重新賦值的變量,包括類屬性、局部變量。
2) 對象參數前加 final,表示不允許修改引用的指向。
3) 類方法確定不允許被重寫。
19.【推薦】慎用 Object的 clone方法來拷貝對象。
說明:對象的 clone方法默認是淺拷貝,若想實現深拷貝需要重寫 clone方法實現屬性對象的拷貝。
推薦博客:
http://blog.csdn.net/zhangjg_blog/article/details/18369201
http://www.cnblogs.com/DreamDrive/p/5430479.html
http://www.cnblogs.com/DreamDrive/p/5430981.html
20.【推薦】類成員與方法訪問控制從嚴:
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方法,或者一
個 public的成員變量,刪除一下,不得手心冒點汗嗎?變量像自己的小孩,盡量在自己的視線內,變量作用域太大,如果無限制的到處跑,那么你會擔心的。
(五) 集合處理
1. 【強制】Map/Set的 key為自定義對象時,必須重寫 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 異常。
4. 【強制】使用集合轉數組的方法,必須使用集合的 toArray(T[] array),傳入的是類型完全一樣的數組,大小就是 list.size()。
反例:直接使用 toArray無參方法存在問題,此方法返回值只能是 Object[]類,若強轉其它類型數組將出現 ClassCastException錯誤。
正例:
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,其它數組元素保持原值,因此最好將方法入參數組大小定義與集合元素個數一致。
下面是toArray帶參數和不帶參數的源碼:
public Object[] toArray() { Object[] result = newObject[size]; System.arraycopy(elementData,0, result, 0,size); return result; } public Object[] toArray(Object a[]){ if (a.length <size){
a =(Object[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(),size);
System.arraycopy(elementData,0, a, 0,size);
}
if (a.length >size) {
a[size] =null;
return a;
}
}
不帶參數的toArray方法,是構造的一個Object數組,然后進行數據拷貝,此時進行轉型就會產生ClassCastException
而帶參數的toArray方法,則是根據參數數組的類型,構造了一個對應類型的,長度跟ArrayList的size一致的空數組,雖然方法本身還是以Object數組的形式返回結果,
不過由於構造數組使用的ComponentType跟需要轉型的ComponentType一致,就不會產生轉型異常 正確的方式
1. Long[] l = (Long []) list.toArray(new Long[0]);
2. Long [] a = new Long[<totalsize>];
Long [] l =(Long []) list.toArray(a);
第2個要注意的是:你要是傳入的參數為9個大小,而list里面有5個object,那么其他的四個很可能是null ,使用的時候要注意。
推薦博客:
http://www.cnblogs.com/DreamDrive/p/5626076.html
5. 【強制】使用工具類 Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方法,它的 add/remove/clear方法會拋出 UnsupportedOperationException異常。
說明:asList的返回對象是一個 Arrays內部類,並沒有實現集合的修改方法。Arrays.asList體現的是適配器模式,只是轉換接口,后台的數據仍是數組。
String[] str = new String[] { "a", "b" };
List list = Arrays.asList(str);
第一種情況:list.add("c"); 運行時異常。
第二種情況:str[0]= "gujin"; 那么 list.get(0)也會隨之修改。
public class Test { public static void main(String[] args) { String[] str = new String[] { "a", "b" }; List list = Arrays.asList(str); list.add("c");//報錯:java.lang.UnsupportedOperationException str[0]= "gujin"; System.out.println(list);//[gujin, b] } }
推薦博客:
http://www.cnblogs.com/DreamDrive/p/5641065.html
http://www.cnblogs.com/DreamDrive/p/5641191.html
6. 【強制】泛型通配符<?extendsT>來接收返回的數據,此寫法的泛型集合不能使用 add方法。
說明:蘋果裝箱后返回一個<? extends Fruits>對象,此對象就不能往里加任何水果,包括蘋果。
7. 【強制】不要在 foreach循環里進行元素的 remove/add操作。remove元素請使用 Iterator方式,如果並發操作,需要對 Iterator對象加鎖。
反例:
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”,會是同樣的結果嗎?
如上 是"1" 打印a [2]
如果是"2" 報錯:java.util.ConcurrentModificationException
正例:
Iterator<String> it = a.iterator(); while(it.hasNext()){
String temp = it.next(); if(刪除元素的條件){ it.remove(); } }
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. 【推薦】集合初始化時,盡量指定集合初始值大小。
說明:ArrayList盡量使用 ArrayList(int initialCapacity) 初始化。
10.【推薦】使用 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.【推薦】高度注意 Map類集合 K/V能不能存儲 null值的情況,如下表格:
反例:很多同學認為 ConcurrentHashMap是可以置入 null值。在批量翻譯場景中,子線程分發時,出現置入 null值的情況,但主線程沒有捕獲到此異常,導致排查困難。
12.【參考】合理利用好集合的有序性(sort)和穩定性(order),避免集合的無序性(unsort)和不穩定性(unorder)帶來的負面影響。
說明:穩定性指集合每次遍歷的元素次序是一定的。有序性是指遍歷的結果是按某種比較規則依次排列的。如:ArrayList是 order/unsort;HashMap是 unorder/unsort;TreeSet是order/sort。
13.【參考】利用 Set元素唯一的特性,可以快速對另一個集合進行去重操作,避免使用 List的contains方法進行遍歷去重操作。
(六) 並發處理
1. 【強制】獲取單例對象要線程安全。在單例對象里面做操作也要保證線程安全。
說明:資源驅動類、工具類、單例工廠類都需要注意。
2. 【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。
說明:使用線程池的好處是減少在創建和銷毀線程上所花的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。
3. 【強制】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代替 Simpledateformatter,官方給出的解釋:simple beautiful strong immutable thread-safe。
4. 【強制】高並發時,同步調用應該去考量鎖的性能損耗。能用無鎖數據結構,就不要用鎖;能鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。
5. 【強制】對多個資源、數據庫表、對象同時加鎖時,需要保持一致的加鎖順序,否則可能會造成死鎖。
說明:線程一需要對表 A、B、C依次全部加鎖后才可以進行更新操作,那么線程二的加鎖順序也必須是 A、B、C,否則可能出現死鎖。
6. 【強制】並發修改同一記錄時,避免更新丟失,要么在應用層加鎖,要么在緩存加鎖,要么在數據庫層使用樂觀鎖,使用 version作為更新依據。
說明:如果每次訪問沖突概率小於20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次
數不得小於3次。
7. 【強制】多線程並行處理定時任務時,Timer運行多個TimeTask時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,使用ScheduledExecutorService則沒有這個問題。
8. 【強制】線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
說明:Executors各個方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:主要問題是堆積的請求處理隊列可能會耗費非常大的內存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:主要問題是線程數最大數是Integer.MAX_VALUE,可能會創建數量非常多的線程,甚至OOM。
9. 【強制】創建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。
正例:
public class TimerTaskThread extends Thread { public TimerTaskThread(){ super.setName("TimerTaskThread"); … }
10.【推薦】使用CountDownLatch進行異步轉同步操作,每個線程退出前必須調用countDown方法,線程執行代碼注意catch異常,確保countDown方法可以執行,避免主線程無法執行至countDown方法,直到超時才返回結果。
說明:注意,子線程拋出異常堆棧,不能在主線程try-catch到。
11.【推薦】避免Random實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一seed導致的性能下降。
說明:Random實例包括java.util.Random 的實例或者 Math.random()實例。
正例:在JDK7之后,可以直接使用APIThreadLocalRandom,在 JDK7之前,可以做到每個線程一個實例。
12.【推薦】通過雙重檢查鎖(double-checkedlocking)(在並發場景)實現延遲初始化的優化問題隱患(可參考 The "Double-Checked Locking is Broken" Declaration),推薦問題解決方
案中較為簡單一種(適用於jdk5及以上版本),將目標屬性聲明為 volatile型(比如反例中修改helper的屬性聲明為private volatile Helper helper = null;);
反例:
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... }
13.【參考】volatile解決多線程內存不可見問題。對於一寫多讀,是可以解決變量同步問題,但是如果多寫,同樣無法解決線程安全問題。如果想取回 count++數據,使用如下類實現:
AtomicInteger count = new AtomicInteger(); count.addAndGet(1); count++操作如果是JDK8,推薦使用 LongAdder對象,比 AtomicLong性能更好(減少樂觀鎖的重試次數)。
14.【參考】注意 HashMap的擴容死鏈,導致 CPU飆升的問題。
15.【參考】ThreadLocal無法解決共享對象的更新問題,ThreadLocal對象建議使用 static修飾。這個變量是針對一個線程內所有操作共有的,所以設置為靜態變量,所有此類實例共享此靜態
變量 ,也就是說在類第一次被使用時裝載,只分配一塊存儲空間,所有此類的對象(只要是這個線程內定義的)都可以操控這個變量。
(七) 控制語句
1. 【強制】在一個 switch塊內,每個 case要么通過 break/return來終止,要么注釋說明程序將繼續執行到哪一個 case為止;在一個 switch塊內,都必須包含一個 default語句並且放在最后,即使它什么代碼也沒有。
2. 【強制】在 if/else/for/while/do語句中必須使用大括號,即使只有一行代碼,避免使用下面的形式:if (condition) statements;
3. 【推薦】推薦盡量少用 else, if-else的方式可以改寫成:
if(condition){ … return obj; }
// 接着寫 else的業務邏輯代碼;
說明:如果使用要 if-elseif-else方式表達邏輯,【強制】請勿超過 3層,超過請使用狀態設計模式。
4. 【推薦】除常用方法(如 getXxx/isXxx)等外,不要在條件判斷中執行復雜的語句,以提高可讀性。
正例:
//偽代碼如下
InputStream stream = file.open(fileName, "w"); if (stream != null) { … }
反例:
if (file.open(fileName, "w") != null)) { … }
5. 【推薦】循環體中的語句要考量性能,以下操作盡量移至循環體外處理,如定義對象、變量、獲取數據庫連接,進行不必要的 try-catch操作(這個 try-catch是否可以移至循環體外)。
6. 【推薦】接口入參保護,這種場景常見的是用於做批量操作的接口。
7. 【參考】方法中需要進行參數校驗的場景:
1) 調用頻次低的方法。
2) 執行時間開銷很大的方法,參數校驗時間幾乎可以忽略不計,但如果因為參數錯誤導致
中間執行回退,或者錯誤,那得不償失。
3) 需要極高穩定性和可用性的方法。
4) 對外提供的開放接口,不管是 RPC/API/HTTP接口。
8. 【參考】方法中不需要參數校驗的場景:
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. 【參考】對於注釋的要求:第一、能夠准確反應設計思想和代碼邏輯;第二、能夠描述業務含義,使別的程序員能夠迅速了解到代碼背后的信息。完全沒有注釋的大段代碼對於閱讀者形同
天書,注釋是給自己看的,即使隔很長時間,也能清晰理解當時的思路;注釋也是給繼任者看的,使其能夠快速接替自己的工作。
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. 【強制】避免用 Apache Beanutils進行屬性的 copy。
說明:Apache BeanUtils性能較差,可以使用其他方案比如 Spring BeanUtils, CglibBeanCopier。
3. 【強制】velocity調用 POJO類的屬性時,建議直接使用屬性名取值即可,模板引擎會自動按規范調用 POJO的 getXxx(),如果是 boolean基本數據類型變量(注意,boolean命名不需要加 is前綴),會自動調用 isXxx()方法。
說明:注意如果是 Boolean包裝類對象,優先調用 getXxx()的方法。
4. 【強制】后台輸送給頁面的變量必須加$!{var}——中間的感嘆號。
說明:如果 var=null或者不存在,那么${var}會直接顯示在頁面上。
5. 【強制】注意 Math.random() 這個方法返回是 double類型,注意取值范圍 0≤x<1(能夠取到零值,注意除零異常),如果想獲取整數類型的隨機數,不要將 x放大 10的若干倍然后取整,直接使用 Random對象的 nextInt或者 nextLong方法。
6. 【強制】獲取當前毫秒數:System.currentTimeMillis(); 而不是 new Date().getTime();
說明:如果想獲取更加精確的納秒級時間值,用 System.nanoTime。在 JDK8中,針對統計時間等場景,推薦使用 Instant類。
7. 【推薦】盡量不要在 vm中加入變量聲明、邏輯運算符,更不要在 vm模板中加入任何復雜的邏輯。
8. 【推薦】任何數據結構的使用都應限制大小。
說明:這點很難完全做到,但很多次的故障都是因為數據結構自增長,結果造成內存被吃光。
9. 【推薦】對於“明確停止使用的代碼和配置”,如方法、變量、類、配置文件、動態配置屬性等要堅決從程序中清理出去,避免造成過多垃圾。清理這類垃圾代碼是技術氣場,不要有這樣的觀念:“不做不錯,多做多錯”。