天天看點

Java編碼規劃最佳實踐

 我們寫了很多的Java代碼,有些代碼包括一些設計一看就非常ugly. 是以需要不斷總結看看哪些設計更優雅一點(文章參考了阿裡的開發規約)。

一、程式設計規約

命名規範:

1、【強制】類名使用UpperCamelCase風格,必須要使用駝峰形式,不包括:DO/BO/DTO/VO/AO

       正例:MarcoPolo / UserDO / UserDTO

2、【強制】常量命名應該全部大寫,單詞間用下劃線隔開,力求語義完整清楚,不要嫌名字太長。

       正例:MAX_STOCK_COUNT

3、【強制】抽象類命名使用Abstract或Base開頭;異常類命名使用Exception結尾;測試類命名以它要測試的類名稱開始,以Test結尾。

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

        反例:不要定義boolean isDeleted這樣的屬性名稱

5、【強制】不允許出現不規範的縮寫

        反例:<業務代碼>AbstractClass 被“縮寫” AbsClass; 會降低代碼的可讀性

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

        正例:pulic class OrderFactory;

7、【推薦】接口類中方法和屬性不要加任何修飾符号(public也不要加),保持代碼的簡潔性,并加javadoc注解。盡量不要在接口定義變量,如果一定要定義肯定是與接口方法相關,并且是整個應用的基礎常量。

8、【參考】枚舉類名建議帶上Enum字尾,枚舉成員名稱需要全大寫,單詞間用下劃線隔開。

        正例:枚舉名字:DealStatusEnum,;成員名稱:SUCCESS / UNKOWN_REASON.

9、【參考】各層命名規約:

       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、【強制】不允許任何魔法值(未經定義的常量)直接出現在代碼裡面。

正例:final String TB_HELLO = ""; 後續引用這個常量值進去

2、【強制】long或Long初始指派時,必須使用L大寫的L,而不能用小寫的l ,很容易看成1了。

3、【推薦】不要使用一個常量類管理全部的常量,應該按常量功能進行歸類,分開維護。比如緩存相關的常量放在類:CacheConsts下

4、【推薦】常量的複用層次有五層:跨應用共享、應用内共享、子工程内共享、包内共享、類内共享

        1)跨應用:打到二方包裡面,A中的常量提供B應用使用

   2)應用内:放到一方庫的modules中constant目錄下。

3)子工程:即在目前子工程的constant目錄下

4)包内:在目前包中的constant目錄下

5)類内共享:直接在類中定義public static final 定義

OOP規約:

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

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

3、【強制】相同參數類型,相同業務含義,才可以使用Java的可變參數,避免使用Object.可變參數放在參數清單最後。建議盡量不要使用可變參數程式設計。

       正例:public User getUsers(String type,Integer... ids){}

4、【強制】外部正在調用或二方庫依賴的接口,不允許修改方法簽名,避免對接口調用産生影響。接口過期必須加@Deprecated注解,并說明采用的新接口或新服務是什麼

        這裡引申了一個問題就是在我們定義接口的時候入參如果參數過多的時候盡量定義一個對象,怕萬一後面添加新的入參,得改接口就比較麻煩。

5、【強制】盡量不要使用過時的類或方法

6、【強制】object的equals方法容易抛空指針,推薦使用java.util.Objects#equals(jdk7引入的工具類)

7、【強制】所有相同類型的包裝類對象之間的值比較,使用使用equals方法進行比較。

       說明:對于Integer var=? 如果是在-128到127之間進行指派,這個範圍内的Integer值是在IntegerCache.cache産生,會複用已有對象,這個範圍内的值可以直接使用==進行判斷,如果在這個區間之外的所有資料都會在堆上産生并不會複用已有對象,這是一個大坑,使用==就會有大問題,得使用equals.

8、【強制】關于基本資料類型與包裝資料類型的使用标準如下:

1)所有POJO類屬性必須使用包裝資料類型(不要對POJO裡面定義基本資料類型切記!)

2)RPC方法的傳回值和參數必須使用包裝資料類型

3)所有局部變量推薦使用基本資料類型

正例:資料庫查詢的結果可能為null, 因為自動拆箱,如果用基本資料類型接收會有NPE風險.

9、【強制】定義POJO類時不要設定任何屬性的預設值

       比如某個DO裡面的gmtCreate預設值為new Date(). 這樣就會産生一個預設值。

10、【強制】序列化類新增屬性時,不要修改serialVersionUID字段,避免反序列失敗;如果完全不相容更新,避免反序列化混亂,那就修改這個值

11、【強制】構造方法裡面禁止加入任何業務邏輯,如果有初始化邏輯,放到init方法中。

12、【強制】POJO類必須寫toString方法。

13、【推薦】循環體内,字元串聯接方法使用StringBuilder的append方法進行擴充。而不要使用 + 進行字元串連接配接

14、【推薦】慎用Object的clone方法來拷貝對象。

說明 :對象的clone預設是淺拷貝,若想深拷貝則需要重寫clone 方法實作屬性的拷貝

并發處理:

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

2、【強制】建立線程或線程池需要指定有意義的名稱,友善出錯排查

3、【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式建立線程。

        使用線程池的好處可以減少在建立和銷毀線程上所花的時間及資源開銷。如果不使用線程池,可能會造成大量的線程建立消耗問題。

4、【強制】線程池不允許使用Executors建立,而是通過ThreadPoolExecutor方式。

5、【強制】SimpleDateFormat是線程不安全的類,一般不要定義成static,如果定義成static,則必須加鎖,或者使用DateUtils工具類。

正例:注意線程安全,使用DateUtils.

6、【強制】必須回收自定義的ThreadLocal變量,尤其線上程池場景下,線程會複用,如果不清理自定義ThreadLocal變量可能會影響後續業務邏輯和造成記憶體洩露問題,盡量在try-finally進行回收。

正例:objectThreadLocal.set(x); try {...} finally{ objectThreadLocal.remove();}

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

說明:讓鎖的代碼塊工作量盡可能小。

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

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

正例:可以使用tairManager方法:incr(namespace,loclKey,1,0,expireTime);判斷傳回步長是否為1,實作分布式鎖;借助緩存可以實作分布式鎖

9、【推薦】資金相關金融資訊使用悲觀鎖政策

10、【參考】HashMap在容量不夠進行resize由于高并發可能出現死鍊,導緻CPU飙升。

其他:

1、【強制】避免用apache beanutils進行屬性copy

可以使用其他方案如SpringBeanUtils, CglibBeanCopier

2、【強制】背景輸送給頁面的變量必須加$!{var}

3、【推薦】任何資料結構的構造或初始化,都應指定大小,避免資料結構無限增長把記憶體吃光。

二、異常日志

異常處理:

1、【強制】異常不要用來控制流程控制,條件控制,因為異常處理效率比較低

2、【強制】對大段代碼進行try-catch,這是不負責的表現,catch時請厘清穩定代碼和非穩定代碼。穩定代碼是指不論如何都不會出錯。對于非穩定的代碼也要細分異常類型

3、【強制】捕獲了異常就要處理它,如果不想處理就往上抛,讓調用方來處理這個異常

4、【強制】事務場景中,抛出異常被catch後,如果需要復原,一定要手動復原事務

5、【推薦】防止NPE,是程式員的基本修養,注意NPE産生的場景:

1)傳回類型為基本資料類型,return包裝資料類型的對象時自動拆箱有可能産生NPE。

2)資料庫查詢結果可能為null

3)集合的元素即使isNotEmpty但取出來的資料元素也可能是null

4)遠端調用傳回對象時,一律要做空指針判斷

5)對session中擷取的資料,建議NPE檢查

6)級聯調用obj.getA().getB().getC() 易産生NPE

6、【推薦】在代碼中使用“抛異常”還是“傳回錯誤碼”,對于對外提供的必須使用錯碼碼,而應用内部推薦異常抛出;跨應用間HSF調用優先考慮使用Result方式,封裝isSuccess()方法、錯誤碼、錯誤簡短資訊。

7、【參考】避免出現重複代碼

盡量抽取出來公用子產品、公共類、公共方法

三、應用分層

1、【推薦】推薦如下的分層結構

Java編碼規劃最佳實踐

1)開放接口層:可直接封裝service接口暴露RPC接口;通過web封裝成http接口;網關控制層等;

2)終端顯示層:各個端的模闆渲染并執行顯示層,比如JS渲染、JSP渲染

3)WEB層:主要是對通路控制進行轉發,各類基本參數校驗

4)service層:相對具體的業務邏輯服務層

5)manager層:通用業務處理層(核心邏輯處理在此層)

a) 對第三方平台封裝的層,預處理傳回結果及轉換異常資訊

b) 對service層通用能力下沉,如緩存方案、中間件通用處理(比如定時任務、消息發送)

c) 與DAO互動,對DAO的業務通用能力封裝。

6)DAO層:資料通路層,負責寫DAL的代碼

7)外部接口或第三方平台:包括其他部門的RPC接口,HTTP接口。

說說我的了解:

controller    schduleX    hsf   服務提供層 這一層需要提供統一的攔截器(對方法的入參做攔截+對異常做統一攔截)

通用業務處理層(Biz)(接口驅動:即事先定義出來接口比如Hsf接口\controller層接口)再封裝各種業務Biz業務

領域對象(含DAO\Serivce\Domain層的東西)      【外部系統對接層】管這一層叫manage層比如調用其他系統擷取資料

2、【參考】(分層之後的異正常約)在DAO層,産生的異常類型有很多,無法用細粒度異常進行catch,就需要throw new DAOException(e),但不需要打日志,讓上層manage/service層進行捕獲并打到日志檔案裡面。這樣就可以避免浪費性能和日志存儲。

3、【參考】分層領域模型規約:

1)DO:與資料庫表結構一、一對應,通過DAO層向上傳輸資料源對象

2)DTO:資料傳輸對象,service/manager向外傳輸的對象(!!!在manage層就是DTO了注意不是DO!)

3)BO:業務對象,可以由service層輸出的封裝業務邏輯的對象

4)Query: 資料查詢對象,各層接收上層的查詢請求,超過2個參數的查詢封裝,禁用map來傳輸。而是定義對象

5)VO:顯示層。通常是WEB向模闆渲染引擎傳輸的對象

繼續閱讀