阿里巴巴java开发手册学习
本文對阿里巴巴java開發手冊中需要注意的點予以記錄
1.編程規約
-
類名中包含領域模型如DO/BO/DTO/VO時要 全部大寫,如UserDTO.
-
抽象類要以Abstract或Base開頭,異常類以Exception結尾,測試類要以所測試的類開頭,Test結尾。
-
杜絕不規范的縮寫
-
將設計模式體現在類名中,有利于閱讀者快速理解架構,如OrderFactory LoginProxy ResourceObsever
-
service和dao層的方法命名推薦:get list count save/insert remove/delete update作為前綴
-
不要使用一個常量類來維護所有常量,應該分類分開維護
-
任何運算符前后都應該有一個空格
-
所有覆寫方法,必須添加@Override注解
-
POJO類必須寫toString方法,便于排查問題(拋出異常時會調用該類的toString打印屬性值)
-
final可以提高程序效率,多考慮屬性,方法,類是否可以定義成final
-
關于 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 異常。但實際開發中subList基本不會使用。
-
使用集合轉數組的方法,必須使用集合的 toArray(T[] array) ,傳入的是類型完全一樣的數組,大小就是 list . size() 。
反例:直接使用 toArray 無參方法存在問題,此方法返回值只能是 Object[] 類,若強轉其它類型數組將出現 ClassCastException 錯誤。
正例:
說明:使用 toArray 帶參方法,入參分配的數組空間不夠大時, toArray 方法內部將重新分配內存空間,并返回新數組地址 ; 如果數組元素大于實際所需,下標為 [ list . size() ] 的數組元素將被置為 null ,其它數組元素保持原值,因此最好將方法入參數組大小定義與集合元素個數一致。
- 使用工具類 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) 也會隨之修改。
如圖:返回的是Arrays的內部類
- 【強制】不要在 foreach 循環里進行元素的 remove / add 操作。 remove 元素請使用 Iterator方式,如果并發操作,需要對 Iterator 對象加鎖。
反例:
說明:以上代碼的執行結果肯定會出乎大家的意料,那么試一下把“1”換成“2”,會是同樣的結果嗎?會報ConcurrentModificationException并發修改異常。
正例:
- 集合初始化時,盡量指定集合初始值大小。
說明: ArrayList 盡量使用 ArrayList(int initialCapacity) 初始化 - 使用 entrySet 遍歷 Map 類集合 KV ,而不是 keySet 方式進行遍歷。
說明: keySet 其實是遍歷了 2 次,一次是轉為 Iterator 對象,另一次是從 hashMap 中取出key 所對應的 value 。而 entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效率更高。 values() 返回的是 V 值集合,是一個 list 集合對象 ;keySet() 返回的是 K 值集合,是一個 Set 集合對象 ;entrySet() 返回的是 K - V 值組合集合。
map的四種遍歷方式
如果是 JDK 8,使用 Map . foreach 方法。
- 由于 HashMap 的干擾,很多人認為 ConcurrentHashMap 是可以置入 null 值,注意存儲null 值時會拋出 NPE 異常
| Hashtable | 不允許為 null | 不允許為 null | Dictionary | 線程安全 |
| ConcurrentHashMap | 不允許為 null | 不允許為 null | AbstractMap | 分段鎖技術 |
| TreeMap | 不允許為 null | 允許為 null | AbstractMap | 線程不安全 |
| HashMap | 不允許為 null | 不允許為 null | AbstractMap | 線程不安全 |
2.并發處理
- 線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。使用線程池的好處是減少在創建和銷毀線程上所花的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
說明: Executors 返回的線程池對象的弊端如下:
1) FixedThreadPool 和 SingleThreadPool :
允許的請求隊列長度為 Integer.MAX_VALUE ,可能會堆積大量的請求,從而導致 OOM 。
2) CachedThreadPool 和 ScheduledThreadPool :
允許的創建線程數量為 Integer.MAX_VALUE ,可能會創建大量的線程,從而導致 OOM
而且注意自定義的線程池一定要定義有意義的線程池名稱,便于查看日志排查問題。
- SimpleDateFormat 是線程不安全的類,一般不要定義為 static 變量,如果定義為static ,必須加鎖,或者使用 DateUtils 工具類。
正例:注意線程安全,使用 DateUtils 。亦推薦如下處理:
說明:如果是 JDK 8 ,可以使用 Instant 代替 Date , LocalDateTime 代替 Calendar ,DateTimeFormatter 代替 Simpledateformatter ,官方給出的解釋: simple beautiful strong immutable thread - safe 。
3.邏輯處理
- 在一個 switch 塊內,每個 case 要么通過 break / return 等來終止,要么注釋說明程序將繼續執行到哪一個 case 為止 ; 在一個 switch 塊內,都必須包含一個 default 語句并且放在最后,即使它什么代碼也沒有
- 推薦盡量少用 else , if - else 的方式可以改寫成:
if(condition){
…
return obj;
}
// 接著寫 else 的業務邏輯代碼;
說明:如果非得使用 if()…else if()…else… 方式表達邏輯,【強制】請勿超過 3 層,超過請使用狀態設計模式。
正例:邏輯上超過 3 層的 if-else 代碼可以使用switch語句,或者狀態模式來實現 - 方法中需要進行參數校驗的場景:
1 ) 調用頻次低的方法。
2 ) 執行時間開銷很大的方法,參數校驗時間幾乎可以忽略不計,但如果因為參數錯誤導致
中間執行回退,或者錯誤,那得不償失。
3 ) 需要極高穩定性和可用性的方法。
4 ) 對外提供的開放接口,不管是 RPC / API / HTTP 接口。
5) 敏感權限入口 - 方法中不需要參數校驗的場景:
1 ) 極有可能被循環調用的方法,不建議對參數進行校驗。但在方法說明里必須注明外部參數檢查。
2 ) 底層的方法調用頻度都比較高,一般不校驗。畢竟是像純凈水過濾的最后一道,參數錯誤不太可能到底層才會暴露問題。一般 DAO 層與 Service 層都在同一個應用中,部署在同一臺服務器中,所以 DAO 的參數校驗,可以省略。
3 ) 被聲明成 private 只會被自己代碼所調用的方法,如果能夠確定調用方法的代碼傳入參數已經做過檢查或者肯定不會有問題,此時可以不校驗參數 - 所有的枚舉類型字段必須要有注釋,說明每個數據項的用途.但一般枚舉都有description屬性來描述意義。
- 獲取當前毫秒數 System . currentTimeMillis(); 而不是 new Date() . getTime();
說明:如果想獲取更加精確的納秒級時間值,用 System . nanoTime() 。在 JDK 8 中,針對統計時間等場景,推薦使用 Instant 類。
4.異常處理和日志記錄
- 有 try 塊放到了事務代碼中, catch 異常后,如果需要回滾事務,一定要注意手動回滾事務。
- 不能在 finally 塊中使用 return , finally 塊中的 return 返回后方法結束執行,不會再執行 try 塊中的 return 語句
- 方法的返回值可以為 null ,不強制返回空集合,或者空對象等,必須添加注釋充分說明什么情況下會返回 null 值。調用方需要進行 null 判斷防止 NPE 問題。
說明:本規約明確防止 NPE 是調用者的責任。即使被調用方法返回空集合或者空對象,對調用者來說,也并非高枕無憂,必須考慮到遠程調用失敗,運行時異常等場景返回 null 的情況 - 【推薦】防止 NPE ,是程序員的基本修養,注意 NPE 產生的場景:
1 ) 返回類型為包裝數據類型,有可能是 null ,返回 int 值時注意判空。
反例: public int f() { return Integer 對象}; 如果為 null ,自動解箱拋 NPE 。
2 ) 數據庫的查詢結果可能為 null 。注:其實現在很多框架在返回查詢結果時已經進行處理不會返回null
3 ) 集合里的元素即使 isNotEmpty ,取出的數據元素也可能為 null 。
4 ) 遠程調用返回對象,一律要求進行 NPE 判斷。
5 ) 對于 Session 中獲取的數據,建議 NPE 檢查,避免空指針。
6 ) 級聯調用 obj . getA() . getB() . getC(); 一連串調用,易產生 NPE - 在代碼中使用“拋異常”還是“返回錯誤碼”,對于公司外的 http / api 開放接口必須使用錯誤碼 ; 而應用內部推薦異常拋出 ; 跨應用間 RPC 調用優先考慮使用 Result 方式,封裝success 、code、msg。
說明:關于 RPC 方法返回方式使用 Result 方式的理由:
1 ) 使用拋異常返回方式,調用方如果沒有捕獲到就會產生運行時錯誤。
2 ) 如果不加棧信息,只是 new 自定義異常,加入自己的理解的 error message ,對于調用端解決問題的幫助不會太多。如果加了棧信息,在頻繁調用出錯的情況下,數據序列化和傳輸的性能損耗也是問題 - 應用中不可直接使用日志系統 (Log 4 j 、 Logback) 中的 API ,而應依賴使用日志框架SLF 4 J 中的 API ,使用門面模式的日志框架,有利于維護和各個類的日志處理方式統一。
注:這里變量名logger也并沒有大寫。
- 日志文件推薦至少保存 15 天,因為有些異常具備以“周”為頻次發生的特點
- 應用中的擴展日志 ( 如打點、臨時監控、訪問日志等 ) 命名方式:appName _ logType _ logName . log 。 logType :日志類型,推薦分類有stats / desc / monitor / visit 等 ;logName :日志描述。這種命名的好處:通過文件名就可知道日志文件屬于什么應用,什么類型,什么目的,也有利于歸類查找。
正例: mppserver 應用中單獨監控時區轉換異常,如:mppserver _ monitor _ timeZoneConvert . log
說明:推薦對日志進行分類,錯誤日志和業務日志盡量分開存放,便于開發人員查看,也便于通過日志對系統進行及時監控。 - 對 trace / debug / info 級別的日志輸出,必須使用條件輸出形式或者使用占位符的方式。
說明: logger . debug( " Processing trade with id : " + id + " symbol : " + symbol);**如果日志級別是 warn ,上述日志不會打印,但是會執行字符串拼接操作,如果 symbol 是對象,會執行 toString() 方法,浪費了系統資源,執行了上述操作,最終日志卻沒有打印。**原來如此
正例: ( 條件 )
正例: ( 占位符 )
logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);- 異常信息應該包括兩類信息:案發現場信息和異常堆棧信息。如果不處理,那么往上拋。
正例: logger.error(各類參數或者對象 toString + "_" + e.getMessage(), e); e會顯示堆棧信息。 - 謹慎地記錄日志。生產環境禁止輸出 debug 日志 ; 有選擇地輸出 info 日志 ; 如果使用 warn 來記錄剛上線時的業務行為信息,一定要注意日志輸出量的問題,避免把服務器磁盤撐爆,并記得及時刪除這些觀察日志。
說明:大量地輸出無效日志,不利于系統性能提升,也不利于快速定位錯誤點。記錄日志時請思考:這些日志真的有人看嗎?看到這條日志你能做什么?能不能給問題排查帶來好處?
5.數據庫規約
- 單表行數超過 500 萬行或者單表容量超過 2 GB ,才推薦進行分庫分表。
說明:如果預計三年后的數據量根本達不到這個級別,請不要在創建表時就分庫分表 - 業務上具有唯一特性的字段,即使是組合字段,也必須建成唯一索引。
說明:不要以為唯一索引影響了 insert 速度,這個速度損耗可以忽略,但提高查找速度是明顯的 ; 另外,即使在應用層做了非常完善的校驗和控制,只要沒有唯一索引,根據墨菲定律,必然有臟數據產生。注:這個值得考慮 - 超過三個表禁止 join 。需要 join 的字段,數據類型保持絕對一致 ; 多表關聯查詢時,保證被關聯的字段需要有索引。
說明:即使雙表 join 也要注意表索引、 SQL 性能。注:實際上,現在很多都全部采取單表,禁止關聯,簡化數據庫維護。 - 頁面搜索嚴禁左模糊或者全模糊,如果需要請走搜索引擎來解決。
說明:索引文件具有 B - Tree 的最左前綴匹配特性,如果左邊的值未確定,那么無法使用此索引。這個很難做到,如設備名稱。 - 創建索引時避免有如下極端誤解:
1 ) 誤認為一個查詢就需要建一個索引。
2 ) 誤認為索引會消耗空間、嚴重拖慢更新和新增速度。
3 ) 誤認為唯一索引一律需要在應用層通過“先查后插”方式解決 - 在代碼中寫分頁查詢邏輯時,若 count 為 0 應直接返回,避免執行后面的分頁語句
- 不得使用外鍵與級聯,一切外鍵概念必須在應用層解決。
說明: ( 概念解釋 ) 學生表中的 student _ id 是主鍵,那么成績表中的 student _ id 則為外鍵。
如果更新學生表中的 student _ id ,同時觸發成績表中的 student _ id 更新,則為級聯更新。外鍵與級聯更新適用于單機低并發,不適合分布式、高并發集群 ; 級聯更新是強阻塞,存在數據庫更新風暴的風險 ; 外鍵影響數據庫的插入速度。現在基本都設計成單表,在應用中處理關聯邏輯。 - 禁止使用存儲過程,存儲過程難以調試和擴展,更沒有移植性
- 數據訂正時,刪除和修改記錄時,要先 select ,避免出現誤刪除,確認無誤才能執行更新語句。沒毛病更新一般是先根據id查詢出值,再去修改。
- 不要用 resultClass 當返回參數,即使所有類屬性名與數據庫字段一一對應,也需要定義 ; 反過來,每一個表也必然有一個與之對應。
說明:配置映射關系,使字段與 DO 類解耦,方便維護。不能將entity直接作為返回值! - 更新數據表記錄時,必須同時更新記錄對應的 gmt _ modified 字段值為當前時間。規范意思是所有表都應有創建時間和修改時間兩個字段。
- @ Transactional 事務不要濫用。事務會影響數據庫的 QPS ,另外使用事務的地方需要考慮各方面的回滾方案,包括緩存回滾、搜索引擎回滾、消息補償、統計修正等
其他
- 所有 pom 文件中的依賴聲明放在< dependencies >語句塊中,所有版本仲裁放在< dependencyManagement >語句塊中。
說明:< dependencyManagement >里只是聲明版本,并不實現引入,因此子項目需要顯式的聲明依賴, version 和 scope 都讀取自父 pom 。而< dependencies >所有聲明在主 pom 的< dependencies >里的依賴都會自動引入,并默認被所有的子項目繼承。
總結
以上是生活随笔為你收集整理的阿里巴巴java开发手册学习的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: windows常用技巧
- 下一篇: spring的aware学习