天天看點

java項目程式設計規約

1.1 命名規約

1. 【強制】代碼中的命名均不能以下劃線或美元符号開始,也不能以下劃線或美元符号結束。

反例:_name / __name /$Object / name_ / name$ / Object$

2. 【強制】代碼中的命名嚴禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。

說明:正确的英文拼寫和文法可以讓閱讀者易于了解,避免歧義。注意,即使純拼音命名方式也要避免采用。

反例:DaZhePromotion [打折] / getPingfenByName() [評分] / int 某變量= 3

正例:taobao / youku / hangzhou 等國際通用的名稱,可視同英文。

3. 【強制】類名使用UpperCamelCase 風格,必須遵從駝峰形式,但以下情形例外:(業務領域模型的相關命名)DO (domain object)/ BO(businessobject) / DTO (data transfer object)/ VO (value object) /DAO(data access object)等。

正例: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 結尾;測試類命名以它要測試的類的名稱開始,以TestCase 結尾。

7. 【強制】中括号是數組類型的一部分,數組定義如下:String[] args;

反例:請勿使用String args[]的方式來定義。

8. 【強制】POJO 類中布爾類型的變量,都不要加is,否則部分架構解析會引起序列化錯誤。

反例:定義為基本資料類型boolean isSuccess的屬性,它的方法也是isSuccess(),RPC架構在反向解析的時候,“以為”對應的屬性名稱是success,導緻屬性擷取不到,進而抛出異常。

9. 【強制】包名統一使用小寫,點分隔符之間有且僅有一個自然語義的英語單詞。包名統一使用單數形式,但是類名如果有複數含義,類名可以使用複數形式。

正例:應用工具類包名為com.ly.cea.open.util、類名為MessageUtils

10. 【強制】杜絕完全不規範的縮寫,避免望文不知義。

反例:AbstractClass“縮寫”命名成AbsClass;condition“縮寫”命名成condi,此類随意縮寫嚴重降低了代碼的可閱讀性。

11. 【推薦】如果使用到了設計模式,建議在類名中展現出具體模式。

說明:将設計模式展現在名字中,有利于閱讀者快速了解架構設計思想。

正例:public classOrderFactory;

public class LoginProxy;

public class ResourceObserver;

12.【推薦】接口類中的方法和屬性加修飾符号public,保持代碼的簡潔性,并加上有效的Javadoc 注釋。盡量不要在接口裡定義變量,如果一定要定義變量,肯定是與接口方法相關,并且是整個應用的基礎常量。

正例:接口方法簽名: public void f();

接口基礎常量表示:String COMPANY = "lianyi";

反例:接口方法定義: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)插入的方法用insert(推薦)或save 做字首。

5)删除的方法用delete(推薦)或remove 做字首。

6)修改的方法用update 做字首。

B) 領域模型命名規約

1)資料對象:xxxDO,xxx 即為資料表名。

2)資料傳輸對象:xxxDTO,xxx 為業務領域相關的名稱。

3)展示對象:xxxVO,xxx 一般為網頁名稱。

4)POJO 是DO/DTO/BO/VO 的統稱,禁止命名成xxxPOJO。

1.2 常量定義

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)應用内共享常量:放置在一方庫的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 staticfinal 定義。

5. 【推薦】如果變量值僅在一個範圍内變化用Enum 類。如果還帶有名稱之外的延伸屬性,必須使用Enum 類,下面正例中的數字就是延伸資訊,表示星期幾。

正例:public Enum{ MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5),SATURDAY(6), SUNDAY(7);}

1.3 格式規約

1. 【強制】大括号的使用約定。如果是大括号内為空,則簡潔地寫成{}即可,不需要換行;如果是非空代碼塊則:

1)左大括号前不換行。

2)左大括号後換行。

3)右大括号前換行。

4)右大括号後還有else 等代碼則不換行;表示終止右大括号後必須換行。

2. 【強制】左括号和後一個字元之間不出現空格;同樣,右括号和前一個字元之間也不出現空格。詳見第5 條下方正例提示。

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(Stringargs[]) {
// 縮進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. 【強制】單行字元數限制不超過 120 個,超出需要換行,換行時遵循如下原則:

1)第二行相對第一行縮進 4 個空格,從第三行開始,不再繼續縮進,參考示例。

2)運算符與下文一起換行。

3)方法調用的點符号與下文一起換行。

4)在多個參數超長,逗号後進行換行。

5)在括号前不要換行,見反例。

正例:

StringBuffer sb = newStringBuffer();
//超過120 個字元的情況下,換行縮進4 個空格,并且方法前的點符号一起換行
sb.append("zi").append("xin")....append("huang")...
.append("huang")...
.append("huang");
反例:
StringBuffer sb = newStringBuffer();
//超過120 個字元的情況下,不要在括号前換行
sb.append("zi").append("xin")...append
("huang");
//參數很多的方法調用可能超過120 個字元,不要在逗号前換行method(args1, args2, args3, ...
, argsX);      

7. 【強制】方法參數在定義和傳入時,多個參數逗号後邊必須加空格。

正例:下例中實參的"a",後邊必須要有一個空格。

method("a","b", "c");

8. 【強制】IDE 的text file encoding 設定為UTF-8; IDE 中檔案的換行符使用Unix 格式,不要使用windows 格式。

9. 【推薦】沒有必要增加若幹空格來使某一行的字元與上一行的相應字元對齊。

正例:

int a = 3;

long b = 4L;

float c = 5F;

StringBuffer sb = newStringBuffer();

說明:增加sb 這個變量,如果需要對齊,則給a、b、c 都要增加幾個空格,在變量比較多的情況下,是一種累贅的事情。

10. 【推薦】方法體内的執行語句組、變量的定義語句組、不同的業務邏輯之間或者不同的語義之間插入一個空行。相同業務邏輯和語義之間不需要插入空行。

說明:沒有必要插入多行空格進行隔開。

1.4 OOP 規約

1. 【強制】避免通過一個類的對象引用通路此類的靜态變量或靜态方法,無謂增加編譯器解析成本,直接用類名來通路即可。

2. 【強制】所有的覆寫方法,必須加@Override 注解。

反例:getObject()與get0bject()的問題。一個是字母的O,一個是數字的0,加@Override可以準确判斷是否覆寫成功。另外,如果在抽象類中對方法簽名進行修改,其實作類會馬上編譯報錯。

3. 【強制】相同參數類型,相同業務含義,才可以使用Java 的可變參數,避免使用Object。說明:可變參數必須放置在參數清單的最後。(提倡同學們盡量不用可變參數程式設計)

正例:public UsergetUsers(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 方法比較。

說明:對于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. 【推薦】使用索引通路用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 方法實作屬性對象的拷貝。

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.5 集合處理

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 becast 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 = newArrayList<String>(2);
list.add("guan");
list.add("bao");
String[] array = newString[list.size()];
array = list.toArray(array);      

說明:使用toArray 帶參方法,入參配置設定的數組空間不夠大時,toArray 方法内部将重新配置設定記憶體空間,并傳回新數組位址;如果數組元素大于實際所需,下标為[ list.size() ]的數組元素将被置為null,其它數組元素保持原值,是以最好将方法入參數組大小定義與集合元素個數一緻。

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)也會随之修改。

6. 【強制】泛型通配符<? extends T>來接收傳回的資料,此寫法的泛型集合不能使用add 方法。

說明:蘋果裝箱後傳回一個<? extends Fruits>對象,此對象就不能往裡加任何水果,包括蘋果。

7. 【強制】不要在foreach 循環裡進行元素的remove/add 操作。remove 元素請使用Iterator方式,如果并發操作,需要對Iterator 對象加鎖。

反例:

List<String> a = newArrayList<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();
}
}      

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 值的情況,如下表格:

集合類 Key Value Super 說明Hashtable 不允許為null 不允許為null Dictionary 線程安全ConcurrentHashMap 不允許為null 不允許為null AbstractMap 分段鎖技術TreeMap 不允許為null 允許為null AbstractMap 線程不安全HashMap 允許為null 允許為null AbstractMap 線程不安全反例:由于HashMap 的幹擾,很多人認為ConcurrentHashMap是可以置入null 值,注意存儲null 值時會抛出NPE 異常。

12. 【參考】合理利用好集合的有序性(sort)和穩定性(order),避免集合的無序性(unsort)和不穩定性(unorder)帶來的負面影響。

說明:穩定性指集合每次周遊的元素次序是一定的。有序性是指周遊的結果是按某種比較規則依次排列的。如:ArrayList 是order/unsort;HashMap 是unorder/unsort;TreeSet 是

order/sort。

13. 【參考】利用Set 元素唯一的特性,可以快速對一個集合進行去重操作,避免使用List 的contains 方法進行周遊、對比、去重操作。

1.6 并發處理

1. 【強制】擷取單例對象需要保證線程安全,其中的方法也要保證線程安全。

說明:資源驅動類、工具類、單例工廠類都需要注意。

2. 【強制】建立線程或線程池時請指定有意義的線程名稱,友善出錯時回溯。

正例:

public class TimerTaskThreadextends 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 finalThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return newSimpleDateFormat("yyyy-MM-dd");
}
};      

說明:如果是JDK8 的應用,可以使用Instant 代替Date,LocalDateTime 代替Calendar,DateTimeFormatter代替Simpledateformatter,官方給出的解釋:simple beautiful strong immutable thread-safe。

6. 【強制】高并發時,同步調用應該去考量鎖的性能損耗。能用無鎖資料結構,就不要用鎖;能鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。

7. 【強制】對多個資源、資料庫表、對象同時加鎖時,需要保持一緻的加鎖順序,否則可能會造成死鎖。

說明:線程一需要對表A、B、C 依次全部加鎖後才可以進行更新操作,那麼線程二的加鎖順序

也必須是A、B、C,否則可能出現死鎖。

8. 【強制】并發修改同一記錄時,避免更新丢失,要麼在應用層加鎖,要麼在緩存加鎖,要麼在資料庫層使用樂觀鎖,使用version 作為更新依據。

說明:如果每次通路沖突機率小于20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數不得小于3 次。

9. 【強制】多線程并行處理定時任務時,Timer 運作多個TimeTask 時,隻要其中之一沒有捕獲抛出的異常,其它任務便會自動終止運作,使用ScheduledExecutorService則沒有這個問題。

10. 【推薦】使用CountDownLatch 進行異步轉同步操作,每個線程退出前必須調用countDown方法,線程執行代碼注意catch 異常,確定countDown 方法可以執行,避免主線程無法執行至countDown 方法,直到逾時才傳回結果。

說明:注意,子線程抛出異常堆棧,不能在主線程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 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 = newAtomicInteger(); count.addAndGet(1); 如果是JDK8,推薦使用LongAdder 對象,比AtomicLong 性能更好(減少樂觀鎖的重試次數)。

14. 【參考】HashMap 在容量不夠進行resize 時由于高并發可能出現死鍊,導緻CPU 飙升,在開發過程中注意規避此風險。

15. 【參考】ThreadLocal 無法解決共享對象的更新問題,ThreadLocal 對象建議使用static修飾。這個變量是針對一個線程内所有操作共有的,是以設定為靜态變量,所有此類執行個體共享此靜态變量,也就是說在類第一次被使用時裝載,隻配置設定一塊存儲空間,所有此類的對象(隻要是這個線程内定義的)都可以操控這個變量。

1.7 控制語句

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 層,超過請使用狀态設計模式。

正例:邏輯上超過 3 層的if-else 代碼可以使用衛語句,或者狀态模式來實作。

4. 【推薦】除常用方法(如 getXxx/isXxx)等外,不要在條件判斷中執行其它複雜的語句,将複雜邏輯判斷的結果指派給一個有意義的布爾變量名,以提高可讀性。

說明:很多 if 語句内的邏輯相當複雜,閱讀者需要分析條件表達式的最終結果,才能明确什麼樣的條件執行什麼樣的語句,那麼,如果閱讀者分析邏輯表達式錯誤呢?

正例:
//僞代碼如下
boolean existed =(file.open(fileName, "w") != null) && (...) || (...);
if (existed) {
...
}
反例:
if ((file.open(fileName,"w") != null) && (...) || (...)) {
...
}      

5. 【推薦】循環體中的語句要考量性能,以下操作盡量移至循環體外處理,如定義對象、變量、擷取資料庫連接配接,進行不必要的try-catch 操作(這個try-catch 是否可以移至循環體外)。

6. 【推薦】接口入參保護,這種場景常見的是用于做批量操作的接口。

7. 【參考】方法中需要進行參數校驗的場景:

1)調用頻次低的方法。

2)執行時間開銷很大的方法,參數校驗時間幾乎可以忽略不計,但如果因為參數錯誤導緻中間執行回退,或者錯誤,那得不償失。

3)需要極高穩定性和可用性的方法。

4)對外提供的開放接口,不管是RPC/API/HTTP 接口。

5)敏感權限入口。

8. 【參考】方法中不需要參數校驗的場景:

1)極有可能被循環調用的方法,不建議對參數進行校驗。但在方法說明裡必須注明外部參

數檢查。

2)底層的方法調用頻度都比較高,一般不校驗。畢竟是像純淨水過濾的最後一道,參數錯

誤不太可能到底層才會暴露問題。一般DAO 層與Service 層都在同一個應用中,部署在同一台伺服器中,是以DAO 的參數校驗,可以省略。

3)被聲明成private 隻會被自己代碼所調用的方法,如果能夠确定調用方法的代碼傳入參

數已經做過檢查或者肯定不會有問題,此時可以不校驗參數。

1.8 注釋規約

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.9 其它

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 類。