記錄一些自己不太熟悉的規(guī)范!
(一) 命名風格
【強制】POJO類中布爾類型的變量不要加is
錯誤:Boolean isDeleted钓辆;正確:Boolean deleted
原因:方法isDeleted()祖搓,部分框架反向解析戳葵,“以為”屬性是deleted,屬性獲取不到坝茎,拋出異常。【推薦】將設(shè)計模式體現(xiàn)在名字中暇番,利于閱讀者快速理解架構(gòu)設(shè)計嗤放。
正例:public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;【推薦】接口類的方法和屬性不加任何修飾符號(public 也不要加),保持代碼的簡潔性壁酬,并加上有效的Javadoc注釋次酌。盡量不要在接口里定義變量,如果一定要定義變量舆乔,肯定是與接口方法相關(guān)和措,并且是整個應(yīng)用的基礎(chǔ)常量。
正例:接口方法簽名:void f(); 接口基礎(chǔ)常量表示:String COMPANY = "alibaba";
反例:接口方法定義:public abstract void f();
說明:JDK8中接口允許有默認實現(xiàn)蜕煌,那么這個default方法派阱,是對所有實現(xiàn)類都有價值的默認實現(xiàn)。接口和實現(xiàn)類的命名有兩套規(guī)則
: 1)【強制】對于Service和DAO類斜纪,基于SOA的理念贫母,暴露出來的服務(wù)一定是接口,內(nèi)部的實現(xiàn)類用Impl的后綴與接口區(qū)別盒刚。
正例:CacheServiceImpl實現(xiàn)CacheService接口腺劣。
2) 【推薦】 如果是形容能力的接口名稱,取對應(yīng)的形容詞做接口名(通常是–able的形式)因块。
正例:AbstractTranslator實現(xiàn) Translatable橘原。【參考】各層命名規(guī)約:
A) Service/DAO層方法命名規(guī)約
1) 獲取單個對象的方法用get做前綴。
2) 獲取多個對象的方法用list做前綴涡上。
3) 獲取統(tǒng)計值的方法用count做前綴趾断。
4) 插入的方法用save/insert做前綴。
5) 刪除的方法用remove/delete做前綴吩愧。
6) 修改的方法用update做前綴芋酌。
B) 領(lǐng)域模型命名規(guī)約
1) 數(shù)據(jù)對象:xxxDO,xxx即為數(shù)據(jù)表名雁佳。
2) 數(shù)據(jù)傳輸對象:xxxDTO脐帝,xxx為業(yè)務(wù)領(lǐng)域相關(guān)的名稱同云。
3) 展示對象:xxxVO,xxx一般為網(wǎng)頁名稱堵腹。
4) POJO是DO/DTO/BO/VO的統(tǒng)稱炸站,禁止命名成xxxPOJO。
(二) 常量定義【推薦】常量的復用層次有五層:跨應(yīng)用共享常量疚顷、應(yīng)用內(nèi)共享常量旱易、子工程內(nèi)共享常量、包內(nèi)共享常量荡含、類內(nèi)共享常量。
1) 跨應(yīng)用共享常量:放置在二方庫中届垫,通常是client.jar中的constant目錄下释液。
2) 應(yīng)用內(nèi)共享常量:放置在一方庫中,通常是modules中的constant目錄下装处。
反例:易懂變量也要統(tǒng)一定義成應(yīng)用內(nèi)共享常量误债,兩位攻城師在兩個類中分別定義了表示“是”的變量:
類A中:public static final String YES = "yes";
類B中:public static final String YES = "y";
A.YES.equals(B.YES),預(yù)期是true妄迁,但實際返回為false寝蹈,導致線上問題。
3) 子工程內(nèi)部共享常量:即在當前子工程的constant目錄下登淘。
4) 包內(nèi)共享常量:即在當前包下單獨的constant目錄下箫老。
5) 類內(nèi)共享常量:直接在類內(nèi)部private static final定義。
(四) OOP規(guī)約
【強制】相同參數(shù)類型黔州,相同業(yè)務(wù)含義耍鬓,才可以使用Java的可變參數(shù),避免使用Object流妻。
說明:可變參數(shù)必須放置在參數(shù)列表的最后牲蜀。
(提倡同學們盡量不用可變參數(shù)編程) 正例:public User getUsers(String type, Integer... ids) {...}【強制】Object的equals方法容易拋空指針異常,應(yīng)使用常量或確定有值的對象來調(diào)用equals绅这。
正例:"test".equals(object);
反例:object.equals("test");
說明:推薦使用java.util.Objects#equals(JDK7引入的工具類)關(guān)于基本數(shù)據(jù)類型與包裝數(shù)據(jù)類型的使用標準如下:
1) 【強制】所有的POJO類屬性必須使用包裝數(shù)據(jù)類型涣达。
2) 【強制】RPC方法的返回值和參數(shù)必須使用包裝數(shù)據(jù)類型。
3) 【推薦】所有的局部變量使用基本數(shù)據(jù)類型证薇。
說明:POJO類屬性沒有初值是提醒使用者在需要使用時度苔,必須自己顯式地進行賦值,任何NPE問題浑度,或者入庫檢查林螃,都由使用者來保證。
正例:數(shù)據(jù)庫的查詢結(jié)果可能是null俺泣,因為自動拆箱疗认,用基本數(shù)據(jù)類型接收有NPE風險完残。
反例:比如顯示成交總額漲跌情況,即正負x%横漏,x為基本數(shù)據(jù)類型谨设,調(diào)用的RPC服務(wù),調(diào)用不成功時缎浇,返回的是默認值扎拣,頁面顯示為0%,
這是不合理的素跺,應(yīng)該顯示成中劃線二蓝。所以包裝數(shù)據(jù)類型的null值,能夠表示額外的信息指厌,如:遠程調(diào)用失敗刊愚,異常退出。【強制】POJO類必須寫toString方法踩验。使用IDE的中工具:source> generate toString時鸥诽,如果繼承了另一個POJO類,注意在前面加一下super.toString箕憾。
說明:在方法執(zhí)行拋出異常時牡借,可以直接調(diào)用POJO的toString()方法打印其屬性值,便于排查問題袭异。【推薦】使用索引訪問用String的split方法得到的數(shù)組時钠龙,需做最后一個分隔符后有無內(nèi)容的檢查,否則會有拋IndexOutOfBoundsException的風險御铃。
說明:
String str = "a,b,c,,";
String[] ary = str.split(",");
// 預(yù)期大于3俊鱼,結(jié)果是3
System.out.println(ary.length);【推薦】類成員與方法訪問控制從嚴:
2) 工具類不允許有public或default構(gòu)造方法。 ( 直接類名調(diào)用靜態(tài)方法畅买,不需要構(gòu)造器實例化 )
(五) 集合處理
【強制】 ArrayList的subList結(jié)果不可強轉(zhuǎn)成ArrayList并闲,否則會拋出ClassCastException異常,
說明:subList 返回的是 ArrayList 的內(nèi)部類 SubList谷羞,并不是 ArrayList 帝火,
而是 ArrayList 的一個視圖,對于SubList子列表的所有操作最終會反映到原列表上湃缎。【強制】在subList場景中犀填,高度注意對原集合元素個數(shù)的修改,
會導致子列表的遍歷嗓违、增加九巡、刪除均會產(chǎn)生ConcurrentModificationException 異常。【強制】使用工具類Arrays.asList()把數(shù)組轉(zhuǎn)換成集合時蹂季,不能使用其修改集合相關(guān)的方法冕广,
它的add/remove/clear方法會拋出UnsupportedOperationException異常疏日。
說明:asList的返回對象是一個Arrays內(nèi)部類,并沒有實現(xiàn)集合的修改方法撒汉。
Arrays.asList體現(xiàn)的是適配器模式沟优,只是轉(zhuǎn)換接口,后臺的數(shù)據(jù)仍是數(shù)組睬辐。
String[] str = new String[] { "you", "wu" }; List list = Arrays.asList(str);
第一種情況:list.add("yangguanbao"); 運行時異常挠阁。
第二種情況:str[0] = "gujin"; 那么list.get(0)也會隨之修改。
如果想修改:
List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3));【強制】泛型通配符<? extends T>來接收返回的數(shù)據(jù)溯饵,此寫法的泛型集合不能使用add方法侵俗,
而<? super T>不能使用get方法,做為接口調(diào)用賦值時易出錯丰刊。
說明:擴展說一下PECS(Producer Extends Consumer Super)原則:
第一隘谣、頻繁往外讀取內(nèi)容的,適合用<? extends T>藻三。
第二统捶、經(jīng)常往里插入的乾蓬,適合用<? super T>葛作。【推薦】集合初始化時烦感,指定集合初始值大小帽芽。
正例:initialCapacity =(需要存儲的元素個數(shù) / 負載因子) + 1稿茉。
注意負載因子(即loader factor)默認為0.75颅夺,如果暫時無法確定初始值大小箕宙,請設(shè)置為16(即默認值)忘衍。
反例:HashMap需要放置1024個元素逾苫,由于沒有設(shè)置容量初始大小,隨著元素不斷增加枚钓,容量7次被迫擴大铅搓,resize需要重建hash表,嚴重影響性能搀捷。【推薦】使用entrySet遍歷Map類集合KV星掰,而不是keySet方式進行遍歷。(兩者都都要遍歷的時候)
說明:keySet其實是遍歷了2次嫩舟,一次是轉(zhuǎn)為Iterator對象氢烘,另一次是從hashMap中取出key所對應(yīng)的value。
而entrySet只是遍歷了一次就把key和value都放到了entry中家厌,效率更高播玖。如果是JDK8,使用Map.foreach方法饭于。 items.forEach((k,v)->{ System.out.println("Item : " + k + " Count : " + v); });
正例:values()返回的是V值集合蜀踏,是一個list集合對象维蒙;keySet()返回的是K值集合,是一個Set集合對象脓斩;entrySet()返回的是K-V值組合集合木西。【推薦】高度注意Map類集合K/V能不能存儲null值的情況,如下表格:
集合類 Key Value Super 說明
Hashtable 不允許為null 不允許為null Dictionary 線程安全
ConcurrentHashMap 不允許為null 不允許為null AbstractMap 鎖分段技術(shù)(JDK8:CAS)
TreeMap 不允許為null 允許為null AbstractMap 線程不安全
HashMap 允許為null 允許為null AbstractMap 線程不安全
(六) 并發(fā)處理
【強制】線程池不允許使用Executors去創(chuàng)建随静,而是通過ThreadPoolExecutor的方式八千,這樣的處理方式讓寫的同學更加明確線程池的運行規(guī)則,規(guī)避資源耗盡的風險燎猛。說明:Executors返回的線程池對象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允許的請求隊列長度為Integer.MAX_VALUE恋捆,可能會堆積大量的請求,從而導致OOM重绷。
2)CachedThreadPool和ScheduledThreadPool:允許的創(chuàng)建線程數(shù)量為Integer.MAX_VALUE沸停,可能會創(chuàng)建大量的線程,從而導致OOM昭卓。【強制】SimpleDateFormat 是線程不安全的類愤钾,一般不要定義為static變量,如果定義為static候醒,必須加鎖能颁,或者使用DateUtils工具類。 正例:注意線程安全倒淫,使用DateUtils伙菊。亦推薦如下處理:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
說明:如果是JDK8的應(yīng)用,可以使用Instant代替Date敌土,LocalDateTime代替Calendar镜硕,DateTimeFormatter代替SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable thread-safe返干。【強制】對多個資源兴枯、數(shù)據(jù)庫表、對象同時加鎖時矩欠,需要保持一致的加鎖順序财剖,否則可能會造成死鎖。 說明:線程一需要對表A晚顷、B峰伙、C依次全部加鎖后才可以進行更新操作,那么線程二的加鎖順序也必須是A该默、B瞳氓、C,否則可能出現(xiàn)死鎖。
【強制】并發(fā)修改同一記錄時匣摘,避免更新丟失店诗,需要加鎖。要么在應(yīng)用層加鎖音榜,要么在緩存加鎖庞瘸,要么在數(shù)據(jù)庫層使用樂觀鎖,使用version作為更新依據(jù)赠叼。 說明:如果每次訪問沖突概率小于20%擦囊,推薦使用樂觀鎖,否則使用悲觀鎖嘴办。樂觀鎖的重試次數(shù)不得小于3次瞬场。
【推薦】使用CountDownLatch進行異步轉(zhuǎn)同步操作,每個線程退出前必須調(diào)用countDown方法涧郊,線程執(zhí)行代碼注意catch異常贯被,確保countDown方法被執(zhí)行到,避免主線程無法執(zhí)行至await方法妆艘,直到超時才返回結(jié)果彤灶。 說明:注意,子線程拋出異常堆棧批旺,不能在主線程try-catch到幌陕。
【參考】volatile解決多線程內(nèi)存不可見問題。對于一寫多讀朱沃,是可以解決變量同步問題苞轿,但是如果多寫茅诱,同樣無法解決線程安全問題逗物。
如果是count++操作,使用如下類實現(xiàn):AtomicInteger count = new AtomicInteger(); count.addAndGet(1);
如果是JDK8瑟俭,推薦使用LongAdder對象翎卓,比AtomicLong性能更好(減少樂觀鎖的重試次數(shù))。【參考】 HashMap在容量不夠進行resize時由于高并發(fā)可能出現(xiàn)死鏈摆寄,導致CPU飆升失暴,
在開發(fā)過程中可以使用其它數(shù)據(jù)結(jié)構(gòu)或加鎖來規(guī)避此風險。【參考】ThreadLocal無法解決共享對象的更新問題微饥,ThreadLocal對象建議使用static修飾逗扒。
這個變量是針對一個線程內(nèi)所有操作共享的,所以設(shè)置為靜態(tài)變量欠橘,所有此類實例共享此靜態(tài)變量 矩肩,
也就是說在類第一次被使用時裝載,只分配一塊存儲空間肃续,所有此類的對象(只要是這個線程內(nèi)定義的)都可以操控這個變量
好處: ThreadLocal來消除共享對象的同步問題
(七) 控制語句
【推薦】表達異常的分支時黍檩,少用if-else方式叉袍,這種方式可以改寫成:
if (condition) {
...
return obj;
}
// 接著寫else的業(yè)務(wù)邏輯代碼;
說明:如果非得使用if()...else if()...else...方式表達邏輯,【強制】避免后續(xù)代碼維護困難刽酱,請勿超過3層喳逛。
正例:超過3層的 if-else 的邏輯判斷代碼可以使用衛(wèi)語句(將復雜的判斷拆分為多個)、策略模式棵里、狀態(tài)模式等來實現(xiàn)【推薦】除常用方法(如getXxx/isXxx)等外润文,不要在條件判斷中執(zhí)行其它復雜的語句,將復雜邏輯判斷的結(jié)果賦值給一個有意義的布爾變量名殿怜,以提高可讀性转唉。 說明:很多if語句內(nèi)的邏輯相當復雜,閱讀者需要分析條件表達式的最終結(jié)果稳捆,才能明確什么樣的條件執(zhí)行什么樣的語句赠法,那么,如果閱讀者分析邏輯表達式錯誤呢乔夯? 正例:
// 偽代碼如下
final boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed) {
...
}【參考】下列情形砖织,需要進行參數(shù)校驗:
1) 調(diào)用頻次低的方法。
2) 執(zhí)行時間開銷很大的方法末荐。此情形中侧纯,參數(shù)校驗時間幾乎可以忽略不計,但如果因為參數(shù)錯誤導致中間執(zhí)行回退甲脏,或者錯誤眶熬,那得不償失。
3) 需要極高穩(wěn)定性和可用性的方法块请。
4) 對外提供的開放接口娜氏,不管是RPC/API/HTTP接口。
5) 敏感權(quán)限入口墩新。【參考】下列情形贸弥,不需要進行參數(shù)校驗:
1) 極有可能被循環(huán)調(diào)用的方法。但在方法說明里必須注明外部參數(shù)檢查要求海渊。
2) 底層調(diào)用頻度比較高的方法绵疲。畢竟是像純凈水過濾的最后一道,參數(shù)錯誤不太可能到底層才會暴露問題臣疑。一般DAO層與Service層都在同一個應(yīng)用中盔憨,部署在同一臺服務(wù)器中,所以DAO的參數(shù)校驗讯沈,可以省略郁岩。
3) 被聲明成private只會被自己代碼所調(diào)用的方法,如果能夠確定調(diào)用方法的代碼傳入?yún)?shù)已經(jīng)做過檢查或者肯定不會有問題,此時可以不校驗參數(shù)驯用。