1. 程式人生 > >Java開發規範(一)

Java開發規範(一)

本文摘自阿里開發規範,是阿里工程師們嚴格遵循的開發標準,同時也是培養自己寫出高質量程式碼的必然要求,不讓自己寫出來的程式碼像個剛畢業的。

1、命名的風格:


1. 程式碼中的命名均不能以下劃線或美元符號開始,也不能以下劃線或美元符號結束。 
反例: _name $name


2. 程式碼中的命名嚴禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。
說明:正確的英文拼寫和語法可以讓閱讀者易於理解,避免歧義。
注意,即使純拼音命名方式也要避免採用。
正例: alibaba / taobao 等國際通用的名稱,可視同英文。 
反例: DaZhePromotion [打折] 

3. 類名使用UpperCamelCase風格,必須遵從駝峰形式,但以下情形例外:DO / BO / DTO / VO / AO 
正例:MarcoPolo / UserDO 
反例:macroPolo / UserDo

4. 方法名、引數名、成員變數、區域性變數都統一使用lowerCamelCase風格,必須遵從駝峰形式。 
正例: localValue / getHttpMessage() / inputUserId


5. 常量命名全部大寫,單詞間用下劃線隔開,力求語義表達完整清楚,不要嫌名字長。 
正例: MAX_STOCK_COUNT 


6. 抽象類命名使用Abstract或Base開頭;異常類命名使用Exception結尾;測試類命名以它要測試的類的名稱開始,以Test結尾。


7. 中括號是陣列型別的一部分,陣列定義如下:String[] args; 
反例:使用String args[]的方式來定義。


8. POJO類中布林型別的變數,都不要加is,否則部分框架解析會引起序列化錯誤。 
反例:定義為基本資料型別Boolean isDeleted;的屬性,它的方法也是isDeleted(),RPC
框架在反向解析的時候,“以為”對應的屬性名稱是deleted,導致屬性獲取不到,進而丟擲異常。


9. 包名統一使用小寫,點分隔符之間有且僅有一個自然語義的英語單詞。
包名統一使用單數形式,但是類名如果有複數含義,類名可以使用複數形式。 
正例: 工具類包名為com.alibaba.open.util、類名為MessageUtils


10. 杜絕完全不規範的縮寫,避免望文不知義。 
反例: AbstractClass“縮寫”命名成AbsClass;此類隨意縮寫嚴重降低了程式碼的可閱讀性。


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。


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 static final定義。


5. 如果變數值僅在一個範圍內變化,且帶有名稱之外的延伸屬性,定義為列舉類。下面正例中的數字就是延伸資訊,表示星期幾。 
正例:public Enum { MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6), SUNDAY(7);}

3、程式碼格式


1. 大括號的使用約定。如果是大括號內為空,則簡潔地寫成{}即可,不需要換行;
如果是非空程式碼塊則: 
1) 左大括號前不換行。 
2) 左大括號後換行。 
3) 右大括號前換行。 
4) 右大括號後還有else等程式碼則不換行;表示終止的右大括號後必須換行。


public static void main(String[] args) {
{
{

}
}
}

2. 左小括號和字元之間不出現空格;同樣,右小括號和字元之間也不出現空格。
反例:if (空格a == b空格)
if (1 == 2) {


}
3. if/for/while/switch/do等保留字與括號之間都必須加空格。


4. 任何二目、三目運算子的左右兩邊都需要加一個空格。 
說明:運算子包括賦值運算子=、邏輯運算子&&、加減乘除符號等。


5. 縮排採用4個空格,禁止使用tab字元。
說明: 如果使用 tab 縮排,必須設定 縮排,必須設定 縮排,必須設定 縮排,必須設定 縮排,必須設定 縮排,必須設定 1個 tab 為 4個空格。 
IDEA 設定 tab 為 4個空格時, 請勿勾選 Use tab character ;而在 eclipse 中,必須勾選 insert spaces for tabs 。
正例: (涉及1-5點)
public static void main(String[] args) {
// 縮排4個空格
String say = "hello";
// 運算子的左右必須有一個空格
int flag = 0;
// 關鍵詞if與括號之間必須有一個空格,括號內的f與左括號,0與右括號不需要空格
if (flag == 0) {
System.out.println(say);
}
// 左大括號前加空格且不換行;左大括號後換行
if (flag == 1) {
System.out.println("world");
// 右大括號前換行,右大括號後有else,不用換行
} else {
System.out.println("ok");


// 在右大括號後直接結束,則必須換行
}
}


6. 單行字元個數限制不超過 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);


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 = new StringBuffer();
說明:增加sb這個變數,如果需要對齊,則給a、b、c都要增加幾個空格,在變數比較多的情況下,是一種累贅的事情。


10.方法體內的執行語句組、變數的定義語句組、不同的業務邏輯之間或者不同的語義之間插入一個空行。
相同業務邏輯和語義之間不需要插入空行。 說明:沒有必要插入多個空行進行隔開。


4、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方法比較。 
說明:對於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 this.data + 100;
} else {
return this.data - 100;
}
}

17. 迴圈體內,字串的連線方式,使用StringBuilder的append方法進行擴充套件。 
說明:反編譯出的位元組碼檔案顯示每次迴圈都會new出一個StringBuilder物件,然後進行append操作,最後通過toString方法返回String物件,造成記憶體資源浪費。
反例:
public static void main(String[] args) {
String str = "start";
for (int i = 0; i < 100; i++) {
str = str + "hello";
}
}


18. final可以宣告類、成員變數、方法、以及本地變數,
下列情況使用final關鍵字: 
1) 不允許被繼承的類,如:String類。 
2) 不允許修改引用的域物件,如:POJO類的域變數。 
3) 不允許被重寫的方法,如:POJO類的setter方法。 
4) 不允許執行過程中重新賦值的區域性變數。 
5) 避免上下文重複使用一個變數,使用final描述可以強制重新定義一個變數,方便更好地進行重構。


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的成員變數,刪除一下,不得手心冒點汗嗎?變數像自己的小孩,儘量在自己的視線內,變數作用域太大,如果無限制的到處跑,那麼你會擔心的。


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 be cast to java.util.ArrayList ; 
說明:subList 返回的是 ArrayList 的內部類 SubList,並不是 ArrayList ,而是 ArrayList 的一個檢視,對於SubList子列表的所有操作最終會反映到原列表上。


3.  在subList場景中,高度注意對原集合元素個數的修改,會導致子列表的遍歷、增加、刪除均產生ConcurrentModificationException 異常。


4. 使用集合轉陣列的方法,必須使用集合的toArray(T[] array),傳入的是型別完全一樣的陣列,大小就是list.size()。
說明:使用toArray帶參方法,入參分配的陣列空間不夠大時,toArray方法內部將重新分配記憶體空間,並返回新陣列地址;
如果陣列元素大於實際所需,下標為[ list.size() ]的陣列元素將被置為null,其它陣列元素保持原值,因此最好將方法入引數組大小定義與集合元素個數一致。 
正例:
public static void main(String[] args) {
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體現的是介面卡模式,只是轉換介面,後臺的資料仍是陣列。 
public static void main(String[] args) {
List<String> list = new ArrayList<String>(2);
list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);

List list1 = Arrays.asList(array);
}
第一種情況:list.add("c"); 執行時異常。 
第二種情況:str[0] = "gujin"; 那麼list.get(0)也會隨之修改。


6. 泛型萬用字元<? extends T>來接收返回的資料,此寫法的泛型集合不能使用add方法,而<? super T>不能使用get方法,做為介面呼叫賦值時易出錯。 
說明:擴充套件說一下PECS(Producer Extends Consumer Super)原則:
1)頻繁往外讀取內容的,適合用上界Extends。
2)經常往裡插入的,適合用下界Super。


7. 不要在foreach迴圈裡進行元素的remove/add操作。remove元素請使用Iterator方式,如果併發操作,需要對Iterator物件加鎖。
正例:
Iterator<String> it = a.iterator();
while (it.hasNext()) {
String temp = it.next();
if (刪除元素的條件) {
it.remove();
}
}
反例:
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”,會是同樣的結果嗎?
java.util.ConcurrentModificationException



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. 集合初始化時,指定集合初始值大小。 
說明:HashMap使用HashMap(int initialCapacity) 初始化, 
正例:
initialCapacity = (需要儲存的元素個數 / 負載因子) + 1。注意 負載因子(即loader factor)預設 為 0.75,如果 暫時 無法確定 初始 值大小,請設定 為 16。 
反例: 
HashMap需要 放置 1024個元素 ,由於 沒有設定容量 初始大小,隨著元素不斷增加容 量 7次被迫擴大, resize需要重建 hash表,嚴重影響效能。 表,嚴重影響效能。


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. 合理利用好集合的有序性(sort)和穩定性(order),避免集合的無序性(unsort)和不穩定性(unorder)帶來的負面影響。 
說明:有序性是指遍歷的結果是按某種比較規則依次排列的。穩定性指集合每次遍歷的元素次序是一定的。
如:ArrayList是order/unsort;HashMap是unorder/unsort;TreeSet是order/sort。


12. 利用Set元素唯一的特性,可以快速對一個集合進行去重操作,避免使用List的contains方法進行遍歷、對比、去重操作。