阿里java開發規範學習筆記 (V1.5華山版)

學習總結

本次開發編碼規範手冊的變更,相比上一版本存在較多的變化。
主要集中在,對於其中的原理有了更深層次的追蹤和了解:
1. 集合元素章節對於集合元素的處理上容易出現問題的地方。
2. 新增敏捷模式下,對設計文檔的要求(哪些必須有,哪些可以無)
3. 對於數據庫查詢語句的規約,索引的使用規約
4. 數據庫字段命名規則,庫表字段命名
5. 單元測試的要求
6. 併發處理JDK8

編程規約

9. 包名統一小寫,且使用單數形式,類名可以使用複數形式
例:
com.alibaba.ai.util.MessageUtils  
10.避免在子父類使用相同的成員變量命名,不同代碼塊的局部變量相同命名,造成可讀性降低
13.標識類型的名詞放在詞尾,以提升辨識度。
例:
startTime workQueue nameList THREAD_COUNT

常量定義

3.不要一個常量類維護所有的常量,按功能歸類,分開維護
4.常量的複用層次有五層:跨應用共享常量、應用內共享常量、子工程內共享常量、包內共享常量、類內共享常量
1) 跨應用共享:放置於二方庫jar包中的constant目錄下
2) 應用內共享常量:放置於一方庫的constant目錄下
3) 子工程內共享常量:當前子工程的constant目錄下
4) 包內共享常量:即在當前包下單獨的constant目錄下
5) 類內共享常量:直接在類內部常量定義 private static final
5.如果變量僅在一個固定範圍內變化,使用enum類型來定義。
個人深入:
如果此字符串經常被使用,在給類的成員變量賦值時,使用String.intern()來降低內存使用。

代碼格式

4. 任何二目、三目應算符的左右兩邊都需要一個空格。運算符包括=、邏輯運算符 || && 運算符 + - *
7.類型強制轉換時,右括號與強制轉換值直接不需要任何空格隔開。
例:
long first = 10000000000L;
int second = (int)first +2;
8.換行規則
1) 第二行相比第1行縮進4個空格,從第三行起不再縮進
2) 運算符與下文一起換行。
3) 方法的點符號與下文一起換行。
4) 多參數換行時,逗號不換行
5) 前括號不換行
11. 單個方法的總行數儘量不超過80行,每行不超過120字符
13.不同邏輯、語義、業務間最多插入一個空行分隔提升可讀性。

OOP規則

2.所有複寫方法,必須加@orerride註解 (IDEA會檢測)
4.過時方法,及時清理或加上@Deprecated註解
5.新增代碼不能使用依賴包中的過時方法
6.使用equals方法時,需常量在前,如果都爲變量,可使用google的空指針包裝類進行判斷
7. 所有整形包裝類對賬之間的值比較,全部使用equals方法進行比較
8. 浮點數之間的等值判斷 ,不能使用==來比較,包裝數據類型不能使用equals
反例:
float a = 1.0f -0.9f
float b = 0.9f -0.8f
if (a == b) {
	預期進入此代碼塊,實際上爲false
}
正例1:
如果在一個誤差範圍內,則認爲相等
float a = 1.0f -0.9f
float b = 0.9f -0.8f
float diff = 1e-6f

if (Math(a-b) < diff) {
	成功 -> 進入代碼塊
}
正例2:
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("1.9");
BigDecimal a = new BigDecimal("0.8");

BigDecimal diffA = a.subtract(b);
BigDecimal diffB = b.subtract(c);

if (x.equals(y) {
	成功 -> 進入代碼塊
}
9. DO 屬性類型要與數據庫字段類型相匹配
-> 原來使用過Mybatis的類型映射String -> int , int -> String 目前看起來不太符合規範
10. 爲了防止精度損失,精緻使用構造方法BigDecimal(double),最多到BigDecimal(float)
示例:
BigDecilaml g = new BigDecimal(0.1f); 實際值爲:0.10000000149
正例:
使用String的方式,Double的toString方法會按照double的實際表達精度對尾數進行了截斷。
BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = new BigDecimal.valueOf(0.1);
11. RPC及數據POJO類,所有屬性使用包裝數據類型,局部變量使用基礎類型。
12. 所有DO / DTO /VO 等數據類初始化時,不要設定任何存在可能被賦值的屬性有默認值。
14.構造方法不能有任何業務邏輯,業務邏輯可以使用init方法或者spring的@poststruts進行初始化。
20.setter /getter方法中,不要增加業務邏輯,增加排查問題的難度
22.final可以什麼類、成員變量、方法、以及本地變量,下列情況使用final關鍵字
1) 不允許被繼承的類
2) 不允許修改引用的域對象
3) 不允許被覆寫的方法
4) 不允許允許過程中重新賦值的局部變量
5) 避免上下文使用同一個變量,使用final可以強制重新定義一個變量,方便更好地進行重構
23 慎用Object 的clone方法進行拷貝對象,如需深拷貝需要重寫clone方法

集合處理

1.關於hashCode和equals的處理
1) 覆寫了equals,就必須覆寫hashCode
2) 因爲Set存在的是不重複對象,根據hashCode和equals進行判斷,所以Set存儲的對象必須覆這兩個方法
3) 如果自定義對象作爲MAP的key,那麼必須覆寫hashCode和quals
2. ArrayList的subList結果不可強轉成ArrayList,否則會拋出ClassCastException異常
說明:subList返回的是ArrayList的內部類SubList,並不是ArrayList,而是ArrayList的一個視圖,對於SubList子列表的所有操作會最終反映到原列表。(在使用SubList,父List 的增加及刪除操作 需要極度謹慎,容易出現ConcurrentModiffcationException )
3. Collections 類返回的對象,如:emptyList()/singletonList()等都是Immutablelist ,不可對其進行田間及刪除元素操作。
說明:這一塊可以看list的初始化賦值,對於當一個元素都沒有的時,被賦值的對象。和添加第1個元素時,生成的對象不是同一個。
5.在subList場景中,**高度注意對原集合元素的增加或刪除**,俊輝導致子列表遍歷、增加、刪除產生concurrentModificationException異常。
6.集合轉數組的方法們,必須使用集合的toArray(T[]) array),傳入的是類型完全一致、長度爲0的空數組。
反例:
直接使用toArray無參方法存在問題,此方法返回值只能是Objectp[]類,若強轉其他類型數組會出現ClassCastException錯誤。
正例:
list<String> list = new ArrayListM<>(2);
list.add("test");
list.add("??");
String[] array = list.toArray(new String[0]);
說明:使用toArray帶參方法,數組空間大小的length選擇:
1) 等於0 ,動態創建於size相同的數組,性能最好。
2) 小於0 但小於size,重建創建大小等於size的數組,增加GC負擔。
3) 等於size,在搞併發情況下,數組創建完成之後,size正在變大的情況下,負面影響與上相同。
4) 大於size,空間浪費,且在sie處插入null值,存在NPE隱患。
7.在使用Collection接口的任何實現類的addAll()方法時,都要對輸入的集合參數進行NPE判斷。
8.使用工具類Arrays.asList()把數組轉換成集合時,不能使用其修改集合的相關方法,它的add/remove/clear方法會拋出UnsupportedOperatiponException異常。
說明:asList的返回對象時一個Arrays內部類,並沒有實現集合的修改方法。Arrays.asList體現的是適配器模式,只能轉換接口,後臺的數據任是數組。
	String[] str = new String[]{"test","???"};
	List list  = Arrays.asList(str);
情況1:list.add("test");運行時異常。
情況2:str[0] = "xxx" ,會修改list中的值,反之修改list也會修改str[]
4. 泛型通配符<? extends T>用於接收返回的數據,此寫法的泛型集合不能使用add方法,<? super T>不能使用get方法,作爲藉口調用時易出錯。
說明:
PECS(Producer Extends Consumer Super)原則:第一、頻繁往外讀取內容的,適合用<? extends T>。第二、經常往裏插入的,適合用<? super T>
個人總結:
    <? extends T> 只能用於方法返回,告訴編譯器此返參的類型的最小繼承邊界爲T,T和T的父類都能接收,但是入參類型無法確定,只能接受null的傳入。
    <? super T>只能用於限定方法入參,告訴編譯器入參只能是T或其子類型,而返參只能用Object類接收。
    ? 既不能用於入參也不能用於返參
示例:
    class Son extends Self extends Super
    
        List<? extends Self> a = new ArrayList<>(); //參數類型上界是Self
        a.add(new Son());//error 
        a.add(new Self());//error
        a.add(new Super());//error
        a.add(null);//error
        Self s1 = a.get(0); //返回類型是確定的Self類,因爲<? extends T> 只能用於方法返回,告訴編譯器此返參的類型的最小繼承邊界爲T,T和T的父類都能接收,但是入參類型無法確定,只能接受null的傳入
        Super s2 = a.get(0); //Self類型可以用Super接收
        Son s3 = a.get(0); //error:子類不能接收父類型參數

        //--------------------------------------

        List<? super Self> b = new ArrayList<>(); //參數類型下界是Self
        b.add(new Son());//ok 只能放入T類型,且滿足T類型的超類至少是Self,換句話說,就是隻能放入Self的子類型
        b.add(new Self());//ok 本身類型也可以
        b.add(new Super());//error 超類不可以
        b.add(null);//ok
        Object o1 = b.get(0);//返回類型是未知的, 因爲<? super T>只能用於限定方法入參,告訴編譯器入參只能是T或其子類型,而返參只能用Object類接收
        Son o2 = b.get(0);//error
        Self o3 = b.get(0);//error
        Super o4 = b.get(0);//error
5. 在無泛型限制定義的集合賦值給泛型限制的集合時,在使用集合元素時,需要進行instantof判斷,避免拋出ClassCastException異常。
反例:
List<String> generics = null;
List notgenerics = new ArrayList(10);
notGenerics.add(new Object());
notGenerics.add(new Integer(1));

generics = notGenerics;
String str = generics.get(0);
6. 不要在foreach循環裏進行元素的remove/add操作。remove元素需要使用Iterator,如果併發,需要加鎖。
正例:
Iterator<String> iterator = list.iterator();
 while(iterator.hasNest()) {
 	String item = iterator.next();
 	if (刪除條件) {
 		iterator.remove();
 	}
 }
7. 使用entry遍歷Map集合的K、V,而不是keySet方式進行遍歷。
注:keySet其實是遍歷了兩次,一次是轉爲Iterator對象,另一次從hashMap中取出key所對應的value。而entrySet只是遍歷了一次就把key和value放到了entry中,效率更高。JDK 8 中可以使用Map.forEach方法。
8. hashMap值空問題
hashTable(ConcurrentHashMap) key value 都不能爲空
hashMap key value 都能爲null
TreeMap value 可以爲null 
8. 對常用數據結構的有序性及穩定性整理。
個人:
ArrayList 無序,穩定
HashMap 無序,不穩定
TreeSet 有序,穩定
有序:按照比較規則依次排列。
穩定,每次遍歷的元素次序是一定的。 
10. 利用Set元素唯一的特徵,可以對一個集合快速去重,避免使用list的contains方法進行遍歷、對比、去重。

併發處理

1. 創建線程池時,請指定有意義的線程名稱,方便出錯時,回溯。
正例:
public class UserThreadFactory implements ThreadFactory {
	
	private final String namePrefix;
	private final AtomicInteger nextId = new AtomicInteger(1);
	
	UserThreadFactory(String whatFeaturOfGroup) {
		namePrefix = "From UserThreadFactory's" + whatFeaturOfGroup + "~Worker~";
	}
	@Override
	public Thread newThread(Runnable task) {
		String name = namePrefix + nexId.getAndIncrement();
		Thread thread = new Thread(null, task, name, 0, false);
		return thread;
	}
}
個人:方便日誌排查
2. 線程資源必須通過線程池提供,不允許在應用自行顯示創建線程。
個人:
?如果使用fork join框架怎麼辦
4. 線程池創建使用Executors去創建會存在問題,應用通過ThreadPoolExecutor的方式去創建。
個人:
對於ThreadPoolExecutor的構造方法裏,阻塞隊列的初始化(長度設定)、運行模型、核心線程數、最大線程數等參數的設置應該有明確的瞭解
5. SimpleDateFormat 是線程不安全,如定義爲靜態共享,則需要加鎖,或使用類似threadLocal方式去保證線程安全。
說明:
JDK8的應用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat。
個人:
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy MM dd");
LocalDate date = LocalDate.parse("2017 06 17", formatter);
System.out.println(formatter.format(date));
6. 必須回收自定義的ThreadLocal變量,儘量在代理中使用try-finally塊進行回收。
7. 高併發時,同步調用應該去考量鎖的性能損耗。能用無鎖數據結構,就不要用鎖。能鎖區塊,就不要鎖整個方法體。能用對象鎖,就不要用類鎖。
個人:鎖粒度 儘量小 儘量使用併發包下的數據結構。
8. 對多個資源、數據庫表、對象同時加鎖時,需要保持一致的加鎖順序,否則可能會造成死鎖(循環等待 + 請求和保持條件)。
例如:A線程獲取鎖順序爲 A C B  ,那麼B線程要獲取 B C 資源時,也應該先B後C。
個人:
死鎖的原因:
互斥條件:進程要求對所分配的資源(如打印機)進行排他性控制,即在一段時間內某 資源僅爲一個進程所佔有。此時若有其他進程請求該資源,則請求進程只能等待。
不剝奪條件:進程所獲得的資源在未使用完畢之前,不能被其他進程強行奪走,即只能 由獲得該資源的進程自己來釋放(只能是主動釋放)。
請求和保持條件:進程已經保持了至少一個資源,但又提出了新的資源請求,而該資源 已被其他進程佔有,此時請求進程被阻塞,但對自己已獲得的資源保持不放。
循環等待條件:存在一種進程資源的循環等待鏈,鏈中每一個進程已獲得的資源同時被 鏈中下一個進程所請求。即存在一個處於等待狀態的進程集合{Pl, P2, ..., pn},其中Pi等 待的資源被P(i+1)佔有(i=0, 1, ..., n-1),Pn等待的資源被P0佔有,如圖2-15所示。
9. 在使用阻塞等待獲取鎖的方式中,必須在try代碼塊之外,並且在加鎖方式與try代碼塊之間沒有可能拋出異常的方法調用,避免加鎖成功後,在finally中無法解鎖,或尚未獲取鎖調用解鎖會產生的IllegalMonitorStateException異常。
正例:
Lock lock  = new XxxLock();
// ..
try {
lock.lock();
} catch (Exception e) {
	// 結束
	return;
} 
try {
	doSomething();
} finally {
	lock.unlock();
}
反例:
參看原文
10. 使用非阻塞嘗試機制來獲取鎖的方式中,進入業務塊之前,必須先判斷是否持有鎖,防止因釋放鎖時,造成的未申請到鎖而拋出的IllegalMinitorStateException 異常。
11. 爲防止同一記錄併發修改,需要加鎖,要麼應用層加鎖,要麼緩存加鎖,要麼數據庫使用樂觀鎖(通過Compare And Update)以Version作爲更新依據。
12. Timer多個TimeTask一個子任務拋出異常,其他任務會停止。
個人:不使用TImer類進行定時任務調度了,推薦使用基於spring的quartz框架 或JDK 78自帶的ScheduledExecutorService來進行定時任務的調度。
13. 資金相關金融敏感信息,使用悲觀鎖。
賬戶鎖 (基於緩存/數據庫)
14. CountDownLatch使用時,子線程需要保證countDown方法被執行到。
個人:
try {
} catch(Exception e) {

} finally {
   countDownLatch.countDown()}
15. 隨機數的Random實例在多線程下存在性能問題。Math.random() 和 Random類
正:使用JDK7 ThreadLocalRandom
另外:部分需要安全要求的隨機數,使用java.security.SecureRandom替換java.util.Random
16. 單例類的雙重檢查鎖,變量名需要申明爲volatile
17. volatile解決多線程內存不可見問題。對於一寫多讀,是可以解決變量同步問題,但是是多寫,統一無法解決線程安全。
18. HashMap在容量不夠進行resize時,由於高併發可能出現死鏈,導致CPU飆升,在開發過程中使用其他數據結構或加鎖來避免此風險。
個人:在能夠確保HashMap 的size情況下,初始化後不發生resize就不會出現該問題。
19. ThreadLocal對象使用static修飾時,只在類初始化時有一份副本,所有類的對象都只能操作只一份副本。
個人:這樣使用不就偏離了ThreadLocal 的應用場景,應該不會有這樣用的場景吧。

後續待做 (TODO)

控制語句

異常及日誌

日誌規約

單元測試

安全規約

MySQL

工程結構

設計規約

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章