白話(huà)阿里巴巴Java開(kāi)發(fā)手冊(cè)(編程規(guī)約)
最近,阿里巴巴發(fā)布了《阿里巴巴Java開(kāi)發(fā)手冊(cè)》,總結(jié)了阿里人多年一線(xiàn)實(shí)戰(zhàn)中積累的研發(fā)流程規(guī)范,這些流程規(guī)范在一定程度上能夠保證最終的項(xiàng)目交付質(zhì)量,通過(guò)限制開(kāi)發(fā)人員的編程風(fēng)格、實(shí)現(xiàn)方式來(lái)避免研發(fā)人員在實(shí)踐中容易犯的錯(cuò)誤,同樣的問(wèn)題大家使用同樣的模式解決,便于后期維護(hù)和擴(kuò)展,確保最終在大規(guī)模協(xié)作的項(xiàng)目中達(dá)成既定目標(biāo)。
無(wú)獨(dú)有偶,筆者去年在公司里負(fù)責(zé)升級(jí)和制定研發(fā)流程、設(shè)計(jì)模板、設(shè)計(jì)標(biāo)準(zhǔn)、代碼標(biāo)準(zhǔn)等規(guī)范,并在實(shí)際工作中進(jìn)行了應(yīng)用和推廣,收效頗豐,也總結(jié)了適合支付平臺(tái)的技術(shù)規(guī)范,由于阿里巴巴Java開(kāi)發(fā)手冊(cè)本身定位為規(guī)約和規(guī)范,語(yǔ)言簡(jiǎn)單、精煉,沒(méi)有太多的解讀和示例,有些條款對(duì)于一般開(kāi)發(fā)人員理解起來(lái)比較困難,本文借著阿里巴巴發(fā)布的Java開(kāi)發(fā)手冊(cè),詳細(xì)解讀Java平臺(tái)下開(kāi)發(fā)規(guī)范和標(biāo)準(zhǔn)的制定和實(shí)施,強(qiáng)調(diào)那些在開(kāi)發(fā)過(guò)程中需要重點(diǎn)關(guān)注的技術(shù)點(diǎn),特別是解決某類(lèi)已識(shí)別問(wèn)題的模式和反模式。
《阿里巴巴Java開(kāi)發(fā)手冊(cè)》分為編程規(guī)約、異常日志、MySQL規(guī)約、工程規(guī)約、安全規(guī)約五大部分,本系列文章以這五部分主題為主線(xiàn),分為五篇文章發(fā)布,本文為系列文章的第一篇-編程規(guī)約,后續(xù)會(huì)盡快發(fā)布其余的文章。
1 命名規(guī)約
1.【強(qiáng)制】 代碼中的命名均不能以下劃線(xiàn)或美元符號(hào)開(kāi)始,也不能以下劃線(xiàn)或美元符號(hào)結(jié)束。
反例: name / __name / $Object / name / name$ / Object$
白話(huà):
這條不夠嚴(yán)格,普通的變量、類(lèi)名、方法名必須使用駝峰式命名,最好不要使用下劃線(xiàn)和美元符號(hào),否則看起來(lái)像腳本語(yǔ)言似得,常量可以使用下劃線(xiàn),但是也不要放在常量開(kāi)頭和結(jié)尾。
2.【強(qiáng)制】 代碼中的命名嚴(yán)禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。
說(shuō)明: 正確的英文拼寫(xiě)和語(yǔ)法可以讓閱讀者易于理解,避免歧義。注意,即使純拼音命名方式 也要避免采用。
反例: DaZhePromotion [打折] / getPingfenByName() [評(píng)分] / int 某變量 = 3
正例: alibaba / taobao / youku / hangzhou 等國(guó)際通用的名稱(chēng),可視同英文。
白話(huà):
中英混合的人種咱不歧視,變量名混合太丑了。
Java編譯器支持Unicode(UTF-8),允許中文命名變量,不過(guò)打中文還是沒(méi)有英文快。
英文!英文起名,洋氣、大方、高大上...
3.【強(qiáng)制】類(lèi)名使用 UpperCamelCase 風(fēng)格,必須遵從駝峰形式,但以下情形例外:(領(lǐng)域模型 的相關(guān)命名)DO / BO / DTO / VO等。
正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
白話(huà):
約定俗成的名稱(chēng)或者縮寫(xiě)例外。
4.【強(qiáng)制】方法名、參數(shù)名、成員變量、局部變量都統(tǒng)一使用 lowerCamelCase 風(fēng)格,必須遵從駝峰形式。
正例: localValue / getHttpMessage() / inputUserId
白話(huà):
約定俗稱(chēng)的名稱(chēng)或者縮寫(xiě)例外。
ID為簡(jiǎn)寫(xiě),Id和ID均可。
5.【強(qiáng)制】常量命名全部大寫(xiě),單詞間用下劃線(xiàn)隔開(kāi),力求語(yǔ)義表達(dá)完整清楚,不要嫌名字長(zhǎng)。
正例: MAX_STOCK_COUNT
反例: MAX_COUNT
白話(huà):
必須全部大寫(xiě),除了字母數(shù)字只可以使用下劃線(xiàn),并且不能用在開(kāi)頭和結(jié)尾。
6.【強(qiáng)制】抽象類(lèi)命名使用 Abstract 或 Base 開(kāi)頭;異常類(lèi)命名使用 Exception 結(jié)尾;測(cè)試類(lèi)命名以它要測(cè)試的類(lèi)的名稱(chēng)開(kāi)始,以 Test 結(jié)尾。
白話(huà):
家里放一瓶敵敵畏,上面不寫(xiě)標(biāo)簽,萬(wàn)一喝大了、渴了、喝了、就慘了,你懂的。
7.【強(qiáng)制】中括號(hào)是數(shù)組類(lèi)型的一部分,數(shù)組定義如下:String[] args;
反例: 使用String args[]的方式來(lái)定義。
白話(huà):
這種語(yǔ)法編譯器也認(rèn),但是我們畢竟寫(xiě)Java程序,而不是寫(xiě)C/C++程序。
這怪Java編譯器小組,一開(kāi)始就不應(yīng)該支持這種語(yǔ)法。
8.【強(qiáng)制】POJO 類(lèi)中布爾類(lèi)型的變量,都不要加 is,否則部分框架解析會(huì)引起序列化錯(cuò)誤。
反例: 定義為基本數(shù)據(jù)類(lèi)型Boolean isSuccess;的屬性,它的方法也是isSuccess(),RPC 框架在反向解析的時(shí)候,“以為”對(duì)應(yīng)的屬性名稱(chēng)是 success,導(dǎo)致屬性獲取不到,進(jìn)而拋出異常。
白話(huà):
一些框架使用getter和setter做序列化,有的根據(jù)屬性本身取值,帶了is前綴就找不到了,變量名不要帶be動(dòng)詞,語(yǔ)法不對(duì),英文補(bǔ)考!
9.【強(qiáng)制】包名統(tǒng)一使用小寫(xiě),點(diǎn)分隔符之間有且僅有一個(gè)自然語(yǔ)義的英語(yǔ)單詞。包名統(tǒng)一使用 單數(shù)形式,但是類(lèi)名如果有復(fù)數(shù)含義,類(lèi)名可以使用復(fù)數(shù)形式。
正例: 應(yīng)用工具類(lèi)包名為com.alibaba.open.util、類(lèi)名為MessageUtils(此規(guī)則參考 spring 的框架結(jié)構(gòu))
白話(huà):
包名大寫(xiě)、帶下劃線(xiàn)等,不專(zhuān)業(yè)、難看、不高大上。
10.【強(qiáng)制】杜絕完全不規(guī)范的縮寫(xiě),避免望文不知義。
反例: AbstractClass“縮寫(xiě)”命名成 AbsClass;condition“縮寫(xiě)”命名成 condi,此類(lèi) 隨意縮寫(xiě)嚴(yán)重降低了代碼的可閱讀性。
白話(huà):
不要太摳,不是太長(zhǎng)的名字直接寫(xiě)上就好,編譯器編譯優(yōu)化后變量名將不存在,會(huì)編譯成相對(duì)于方法堆棧bp指針地址的相對(duì)地址,長(zhǎng)變量名不會(huì)占用更多空間。
英文中的縮寫(xiě)有個(gè)慣例,去掉元音留下輔音即可,不能亂縮寫(xiě)。
11.【推薦】如果使用到了設(shè)計(jì)模式,建議在類(lèi)名中體現(xiàn)出具體模式。
說(shuō)明: 將設(shè)計(jì)模式體現(xiàn)在名字中,有利于閱讀者快速理解架構(gòu)設(shè)計(jì)思想。
正例: public class OrderFactory;
- public class LoginProxy;
- public class ResourceObserver;
白話(huà):
讓全世界都知道你會(huì)設(shè)計(jì)模式,這是個(gè)崇尚顯擺的社會(huì)。
12.【推薦】接口類(lèi)中的方法和屬性不要加任何修飾符號(hào)(public 也不要加),保持代碼的簡(jiǎn)潔 性,并加上有效的 Javadoc 注釋。盡量不要在接口里定義變量,如果一定要定義變量,肯定是與接口方法相關(guān),并且是整個(gè)應(yīng)用的基礎(chǔ)常量。
正例: 接口方法簽名:void f();
13.接口基礎(chǔ)常量表示:String COMPANY = "alibaba";
反例: 接口方法定義:public abstract void f();
說(shuō)明:JDK8 中接口允許有默認(rèn)實(shí)現(xiàn),那么這個(gè) default 方法,是對(duì)所有實(shí)現(xiàn)類(lèi)都有價(jià)值的默 認(rèn)實(shí)現(xiàn)。
白話(huà):
脫了褲子放屁始終有點(diǎn)麻煩。
13.接口和實(shí)現(xiàn)類(lèi)的命名有兩套規(guī)則:
1)【強(qiáng)制】對(duì)于 Service 和 DAO 類(lèi),基于 SOA 的理念,暴露出來(lái)的服務(wù)一定是接口,內(nèi)部的實(shí)現(xiàn)類(lèi)用 Impl 的后綴與接口區(qū)別。
正例: CacheServiceImpl 實(shí)現(xiàn) CacheService 接口。
2)【推薦】 如果是形容能力的接口名稱(chēng),取對(duì)應(yīng)的形容詞做接口名(通常是–able 的形式)。
正例: AbstractTranslator 實(shí)現(xiàn) Translatable。
白話(huà):
嚴(yán)重同意!可是想想Observer和Observable,我就不說(shuō)話(huà)了。
14.【參考】枚舉類(lèi)名建議帶上 Enum 后綴,枚舉成員名稱(chēng)需要全大寫(xiě),單詞間用下劃線(xiàn)隔開(kāi)。
說(shuō)明: 枚舉其實(shí)就是特殊的常量類(lèi),且構(gòu)造方法被默認(rèn)強(qiáng)制是私有。
正例: 枚舉名字:DealStatusEnum,成員名稱(chēng): SUCCESS / UNKOWN_REASON。
白話(huà):
不要駝峰!記住枚舉不要駝峰!總是有好多人枚舉用駝峰。
15.【參考】各層命名規(guī)約:
A) Service/DAO層方法命名規(guī)約
1) 獲取單個(gè)對(duì)象的方法用get做前綴。
2) 獲取多個(gè)對(duì)象的方法用list做前綴。
3) 獲取統(tǒng)計(jì)值的方法用count做前綴。
4) 插入的方法用save(推薦)或insert做前綴。
5) 刪除的方法用remove(推薦)或delete做前綴。
6) 修改的方法用update做前綴。
B) 領(lǐng)域模型命名規(guī)約
1) 數(shù)據(jù)對(duì)象:xxxDO,xxx即為數(shù)據(jù)表名。
2) 數(shù)據(jù)傳輸對(duì)象:xxxDTO,xxx為業(yè)務(wù)領(lǐng)域相關(guān)的名稱(chēng)。
3) 展示對(duì)象:xxxVO,xxx一般為網(wǎng)頁(yè)名稱(chēng)。
4) POJO是DO/DTO/BO/VO的統(tǒng)稱(chēng),禁止命名成xxxPOJO。
白話(huà):
大家都這么認(rèn)為很重要。
2 常量定義
1.【強(qiáng)制】不允許出現(xiàn)任何魔法值(即未經(jīng)定義的常量)直接出現(xiàn)在代碼中。
反例: String key = "Id#taobao_"+tradeId; cache.put(key, value);
白話(huà):
這個(gè)不用說(shuō)了,隨地吐痰和隨地大小便是不應(yīng)該的,新加坡是要鞭刑的!
2.【強(qiáng)制】long 或者 Long 初始賦值時(shí),必須使用大寫(xiě)的 L,不能是小寫(xiě)的 l,小寫(xiě)容易跟數(shù)字 1 混淆,造成誤解。
說(shuō)明: Long a = 2l; 寫(xiě)的是數(shù)字的21,還是Long型的2?
白話(huà):
看看區(qū)塊鏈中用了base58,而不是base64,秒懂什么是從用戶(hù)角度考慮產(chǎn)品設(shè)計(jì)!
3.【推薦】不要使用一個(gè)常量類(lèi)維護(hù)所有常量,應(yīng)該按常量功能進(jìn)行歸類(lèi),分開(kāi)維護(hù)。如:緩存相關(guān)的常量放在類(lèi): CacheConsts 下; 系統(tǒng)配置相關(guān)的常量放在類(lèi): ConfigConsts 下。
說(shuō)明: 大而全的常量類(lèi),非得使用查找功能才能定位到修改的常量,不利于理解和維護(hù)。
白話(huà):
盡量讓功能自閉包,標(biāo)準(zhǔn)是一個(gè)小模塊拷貝出去直接就能用,而不是缺這缺那的,是不是讀者很多時(shí)候拷貝了一套類(lèi),運(yùn)行時(shí)候發(fā)現(xiàn)不能用,缺常量,把常量類(lèi)拷貝過(guò)來(lái),發(fā)現(xiàn)常量類(lèi)中有很多不相關(guān)的常量,還得清理。
4.【推薦】常量的復(fù)用層次有五層: 跨應(yīng)用共享常量、應(yīng)用內(nèi)共享常量、子工程內(nèi)共享常量、包內(nèi)共享常量、類(lèi)內(nèi)共享常量。
1) 跨應(yīng)用共享常量: 放置在二方庫(kù)中,通常是client.jar中的constant目錄下。
2) 應(yīng)用內(nèi)共享常量: 放置在一方庫(kù)的modules中的constant目錄下。
反例: 易懂變量也要統(tǒng)一定義成應(yīng)用內(nèi)共享常量,兩位攻城師在兩個(gè)類(lèi)中分別定義了表示 “是”的變量:
類(lèi)A中: public static final String YES = "yes";
類(lèi)B中: public static final String YES = "y"; A.YES.equals(B.YES),預(yù)期是 true,但實(shí)際返回為 false,導(dǎo)致產(chǎn)生線(xiàn)上問(wèn)題。
3) 子工程內(nèi)部共享常量: 即在當(dāng)前子工程的constant目錄下。
4) 包內(nèi)共享常量: 即在當(dāng)前包下單獨(dú)的constant目錄下。
5) 類(lèi)內(nèi)共享常量: 直接在類(lèi)內(nèi)部private static final定義。
白話(huà):
一方庫(kù)、二方庫(kù)、三方庫(kù),叫法很專(zhuān)業(yè),放在離自己最近的上面一個(gè)層次即可。
5.【推薦】如果變量值僅在一個(gè)范圍內(nèi)變化用 Enum 類(lèi)。如果還帶有名稱(chēng)之外的延伸屬性,必須 使用 Enum 類(lèi),下面正例中的數(shù)字就是延伸信息,表示星期幾。
正例: public Enum { MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6), SUNDAY(7);}
白話(huà):
枚舉值需要定義延伸屬性的場(chǎng)景通常是要持久數(shù)據(jù)庫(kù),或者顯示在界面上。
3 格式規(guī)約
1.【強(qiáng)制】大括號(hào)的使用約定。如果是大括號(hào)內(nèi)為空,則簡(jiǎn)潔地寫(xiě)成{}即可,不需要換行; 如果 是非空代碼塊則:
1) 左大括號(hào)前不換行。
2) 左大括號(hào)后換行。
3) 右大括號(hào)前換行。
4) 右大括號(hào)后還有else等代碼則不換行;表示終止右大括號(hào)后必須換行。
白話(huà):
好風(fēng)格,討厭那種左大括號(hào)前換行的,看不慣。
2.【強(qiáng)制】 左括號(hào)和后一個(gè)字符之間不出現(xiàn)空格; 同樣,右括號(hào)和前一個(gè)字符之間也不出現(xiàn)空 格。詳見(jiàn)第 5 條下方正例提示。
白話(huà):
程序?qū)懲昕梢杂镁庉嬈鞯母袷交δ芨袷交珽clipse中快捷鍵是shift+alt+f,筆者寫(xiě)程序的時(shí)候有個(gè)習(xí)慣,每次謝了一段代碼都會(huì)按ctrl+alt+o、ctrl+alt+f、ctrl+s,相信會(huì)有相同習(xí)慣的同行。
3.【強(qiáng)制】if/for/while/switch/do 等保留字與左右括號(hào)之間都必須加空格。
白話(huà):
程序?qū)懲昕梢杂镁庉嬈鞯母袷交δ芨袷交珽clipse中快捷鍵是shift+alt+f,筆者寫(xiě)程序的時(shí)候有個(gè)習(xí)慣,每次謝了一段代碼都會(huì)按ctrl+alt+o、ctrl+alt+f、ctrl+s,相信會(huì)有相同習(xí)慣的同行。
4.【強(qiáng)制】任何運(yùn)算符左右必須加一個(gè)空格。
說(shuō)明: 運(yùn)算符包括賦值運(yùn)算符=、邏輯運(yùn)算符&&、加減乘除符號(hào)、三目運(yùn)算符等。
白話(huà):
程序?qū)懲昕梢杂镁庉嬈鞯母袷交δ芨袷交珽clipse中快捷鍵是shift+alt+f,筆者寫(xiě)程序的時(shí)候有個(gè)習(xí)慣,每次謝了一段代碼都會(huì)按ctrl+alt+o、ctrl+alt+f、ctrl+s,相信會(huì)有相同習(xí)慣的同行。
5.【強(qiáng)制】縮進(jìn)采用 4 個(gè)空格,禁止使用 tab 字符。
說(shuō)明: 如果使用 tab 縮進(jìn),必須設(shè)置 1 個(gè) tab 為 4 個(gè)空格。IDEA 設(shè)置 tab 為 4 個(gè)空格時(shí),請(qǐng)勿勾選Use tab character; 而在 eclipse 中,必須勾選 insert spaces for tabs。
正例: (涉及1-5點(diǎn))
- public static void main(String[] args) {
- // 縮進(jìn) 4 個(gè)空格
- String say = "hello";
- // 運(yùn)算符的左右必須有一個(gè)空格
- int flag = 0;
- // 關(guān)鍵詞 if 與括號(hào)之間必須有一個(gè)空格,括號(hào)內(nèi)的 f 與左括號(hào),0 與右括號(hào)不需要空格
- if (flag == 0) {
- System.out.println(say);
- }
- // 左大括號(hào)前加空格且不換行;左大括號(hào)后換行
- if (flag == 1) {
- System.out.println("world");
- // 右大括號(hào)前換行,右大括號(hào)后有 else,不用換行
- } else { System.out.println("ok");
- // 在右大括號(hào)后直接結(jié)束,則必須換行
- }
- }
白話(huà):
這樣看慣了,怎么看怎么清晰。
6.【強(qiáng)制】單行字符數(shù)限制不超過(guò) 120 個(gè),超出需要換行,換行時(shí)遵循如下原則:
1) 第二行相對(duì)第一行縮進(jìn) 4 個(gè)空格,從第三行開(kāi)始,不再繼續(xù)縮進(jìn),參考示例。
2) 運(yùn)算符與下文一起換行。
3) 方法調(diào)用的點(diǎn)符號(hào)與下文一起換行。
4) 在多個(gè)參數(shù)超長(zhǎng),逗號(hào)后進(jìn)行換行。
5) 在括號(hào)前不要換行,見(jiàn)反例。
正例:
- StringBuffer sb = new StringBuffer();
- //超過(guò) 120 個(gè)字符的情況下,換行縮進(jìn) 4 個(gè)空格,并且方法前的點(diǎn)符號(hào)一起換行
- sb.append("zi").append("xin")...
- .append("huang")...
- .append("huang")...
- .append("huang");
反例:
- StringBuffer sb = new StringBuffer();
- //超過(guò) 120 個(gè)字符的情況下,不要在括號(hào)前換行
- sb.append("zi").append("xin")...append
- ("huang");
- //參數(shù)很多的方法調(diào)用可能超過(guò) 120 個(gè)字符,不要在逗號(hào)前換行
- method(args1, args2, args3, ...
- , argsX);
白話(huà):
一行代碼盡量不要寫(xiě)太長(zhǎng),長(zhǎng)了拆開(kāi)不就得了。
7.【強(qiáng)制】方法參數(shù)在定義和傳入時(shí),多個(gè)參數(shù)逗號(hào)后邊必須加空格。
正例: 下例中實(shí)參的"a", 后邊必須要有一個(gè)空格。
- method("a", "b", "c");
白話(huà):
不加空格太擠了,就像人沒(méi)長(zhǎng)開(kāi)似得。
8.【強(qiáng)制】IDE的text file encoding設(shè)置為UTF-8; IDE中文件的換行符使用Unix格式, 不要使用 windows 格式。
白話(huà):
請(qǐng)不要用GB字符集,換了環(huán)境總有問(wèn)題,Java程序多數(shù)跑在Linux上,當(dāng)然要用Unix換行符。
9.【推薦】沒(méi)有必要增加若干空格來(lái)使某一行的字符與上一行的相應(yīng)字符對(duì)齊。
正例:
- int a = 3;
- long b = 4L;
- float c = 5F;
- StringBuffer sb = new StringBuffer();
說(shuō)明: 增加 sb 這個(gè)變量,如果需要對(duì)齊,則給 a、b、c 都要增加幾個(gè)空格,在變量比較多的 情況下,是一種累贅的事情。
白話(huà):
沒(méi)必要,沒(méi)必要,那樣反而不好看。
10.【推薦】方法體內(nèi)的執(zhí)行語(yǔ)句組、變量的定義語(yǔ)句組、不同的業(yè)務(wù)邏輯之間或者不同的語(yǔ)義
之間插入一個(gè)空行。相同業(yè)務(wù)邏輯和語(yǔ)義之間不需要插入空行。
說(shuō)明: 沒(méi)有必要插入多行空格進(jìn)行隔開(kāi)。
白話(huà):
和我的習(xí)慣一樣一樣的,一段邏輯空一行。
4 OOP 規(guī)約
1.【強(qiáng)制】避免通過(guò)一個(gè)類(lèi)的對(duì)象引用訪(fǎng)問(wèn)此類(lèi)的靜態(tài)變量或靜態(tài)方法,無(wú)謂增加編譯器解析成
本,直接用類(lèi)名來(lái)訪(fǎng)問(wèn)即可。
白話(huà):
也不直觀(guān),看調(diào)用代碼看不出來(lái)是靜態(tài)方法,容易誤解。
2.【強(qiáng)制】所有的覆寫(xiě)方法,必須加@Override 注解。
反例:getObject()與 get0bject()的問(wèn)題。一個(gè)是字母的 O,一個(gè)是數(shù)字的 0,加@Override 可以準(zhǔn)確判斷是否覆蓋成功。另外,如果在抽象類(lèi)中對(duì)方法簽名進(jìn)行修改,其實(shí)現(xiàn)類(lèi)會(huì)馬上編譯報(bào)錯(cuò)。
白話(huà):
Java和C++不一樣,C++是在父類(lèi)先聲明虛擬函數(shù)子類(lèi)才覆寫(xiě),Java是任何方法都能覆寫(xiě),也可以不覆寫(xiě),所以覆寫(xiě)不覆寫(xiě)是沒(méi)有編譯器檢查的,除非接口中某一個(gè)方法完全沒(méi)有被實(shí)現(xiàn)才會(huì)編譯報(bào)錯(cuò)。
3.【強(qiáng)制】相同參數(shù)類(lèi)型,相同業(yè)務(wù)含義,才可以使用 Java 的可變參數(shù),避免使用 Object。
說(shuō)明: 可變參數(shù)必須放置在參數(shù)列表的最后。(提倡同學(xué)們盡量不用可變參數(shù)編程)
正例: public User getUsers(String type, Integer... ids)
白話(huà):
用處不大,可以用重載方法或者數(shù)組參數(shù)代替。
一般應(yīng)用在日志的 API 定義上,用于傳不定的日志參數(shù)。
4.【強(qiáng)制】外部正在調(diào)用或者二方庫(kù)依賴(lài)的接口,不允許修改方法簽名,避免對(duì)接口調(diào)用方產(chǎn)生 影響。接口過(guò)時(shí)必須加@Deprecated 注解,并清晰地說(shuō)明采用的新接口或者新服務(wù)是什么。
白話(huà):
設(shè)計(jì)時(shí)沒(méi)有考慮周全,需要改造接口,需要通過(guò)增加新接口,遷移后下線(xiàn)老接口的方式實(shí)現(xiàn)。
REST接口只能增加參數(shù),不能減少參數(shù),返回值的內(nèi)容也是只增不減。
5.【強(qiáng)制】不能使用過(guò)時(shí)的類(lèi)或方法。
說(shuō)明: java.net.URLDecoder 中的方法 decode(String encodeStr) 這個(gè)方法已經(jīng)過(guò)時(shí),應(yīng)該使用雙參數(shù) decode(String source, String encode)。接口提供方既然明確是過(guò)時(shí)接口,那么有義務(wù)同時(shí)提供新的接口; 作為調(diào)用方來(lái)說(shuō),有義務(wù)去考證過(guò)時(shí)方法的新實(shí)現(xiàn)是什么。
白話(huà):
明確了責(zé)任和義務(wù),接口提供方也有義務(wù)推動(dòng)接口使用方盡早遷移,不要積累技術(shù)負(fù)債。
6.【強(qiáng)制】Object 的 equals 方法容易拋空指針異常,應(yīng)使用常量或確定有值的對(duì)象來(lái)調(diào)用 equals。
正例: "test".equals(object);
反例: object.equals("test");
說(shuō)明: 推薦使用java.util.Objects#equals (JDK7引入的工具類(lèi))
白話(huà):
常量比變量,永遠(yuǎn)都不變的原則。
7.【強(qiáng)制】所有的相同類(lèi)型的包裝類(lèi)對(duì)象之間值的比較,全部使用 equals 方法比較。
說(shuō)明: 對(duì)于Integer var = ?在-128至127之間的賦值,Integer對(duì)象是在 IntegerCache.cache 產(chǎn)生,會(huì)復(fù)用已有對(duì)象,這個(gè)區(qū)間內(nèi)的 Integer 值可以直接使用==進(jìn)行 判斷,但是這個(gè)區(qū)間之外的所有數(shù)據(jù),都會(huì)在堆上產(chǎn)生,并不會(huì)復(fù)用已有對(duì)象,這是一個(gè)大坑, 推薦使用 equals 方法進(jìn)行判斷。
白話(huà):
Java世界里相等請(qǐng)用equals方法,==表示對(duì)象相等,一般在框架開(kāi)發(fā)中會(huì)用到。
關(guān)于基本數(shù)據(jù)類(lèi)型與包裝數(shù)據(jù)類(lèi)型的使用標(biāo)準(zhǔn)如下:
1) 【強(qiáng)制】所有的POJO類(lèi)屬性必須使用包裝數(shù)據(jù)類(lèi)型。
2) 【強(qiáng)制】RPC方法的返回值和參數(shù)必須使用包裝數(shù)據(jù)類(lèi)型。
3) 【推薦】所有的局部變量使用基本數(shù)據(jù)類(lèi)型。
說(shuō)明: POJO 類(lèi)屬性沒(méi)有初值是提醒使用者在需要使用時(shí),必須自己顯式地進(jìn)行賦值,任何
NPE 問(wèn)題,或者入庫(kù)檢查,都由使用者來(lái)保證。
正例: 數(shù)據(jù)庫(kù)的查詢(xún)結(jié)果可能是 null,因?yàn)樽詣?dòng)拆箱,用基本數(shù)據(jù)類(lèi)型接收有 NPE 風(fēng)險(xiǎn)。
反例: 比如顯示成交總額漲跌情況,即正負(fù) x%,x 為基本數(shù)據(jù)類(lèi)型,調(diào)用的 RPC 服務(wù),調(diào)用不成功時(shí),返回的是默認(rèn)值,頁(yè)面顯示:0%,這是不合理的,應(yīng)該顯示成中劃線(xiàn)-。所以包裝數(shù)據(jù)類(lèi)型的 null 值,能夠表示額外的信息,如:遠(yuǎn)程調(diào)用失敗,異常退出。
白話(huà):
其實(shí)包裝數(shù)據(jù)類(lèi)型與基本數(shù)據(jù)類(lèi)型相比,增加了一個(gè)null的狀態(tài),可以攜帶更多的語(yǔ)義。
9.【強(qiáng)制】定義 DO/DTO/VO 等 POJO 類(lèi)時(shí),不要設(shè)定任何屬性默認(rèn)值。
反例: POJO類(lèi)的gmtCreate默認(rèn)值為new Date(); 但是這個(gè)屬性在數(shù)據(jù)提取時(shí)并沒(méi)有置入具體值,在更新其它字段時(shí)又附帶更新了此字段,導(dǎo)致創(chuàng)建時(shí)間被修改成當(dāng)前時(shí)間。
白話(huà):
雖然這里反例不太容易看懂,但是要記得持久領(lǐng)域?qū)ο笾坝蓱?yīng)用層統(tǒng)一賦值gmtCreate和gmtModify字段。
10.【強(qiáng)制】序列化類(lèi)新增屬性時(shí),請(qǐng)不要修改 serialVersionUID 字段,避免反序列失敗; 如 果完全不兼容升級(jí),避免反序列化混亂,那么請(qǐng)修改 serialVersionUID 值。
說(shuō)明:注意 serialVersionUID 不一致會(huì)拋出序列化運(yùn)行時(shí)異常。
白話(huà):
不到萬(wàn)不得已不要使用JDK自身的序列化,機(jī)制很重,信息冗余有版本。
11.【強(qiáng)制】構(gòu)造方法里面禁止加入任何業(yè)務(wù)邏輯,如果有初始化邏輯,請(qǐng)放在 init 方法中。
白話(huà):
這樣做一種是規(guī)范,代碼清晰,還有就是異常堆棧上更容易識(shí)別出錯(cuò)的方法和語(yǔ)句。
12.【強(qiáng)制】POJO 類(lèi)必須寫(xiě) toString 方法。使用 IDE 的中工具:source> generate toString 時(shí),如果繼承了另一個(gè) POJO 類(lèi),注意在前面加一下 super.toString。
說(shuō)明: 在方法執(zhí)行拋出異常時(shí),可以直接調(diào)用 POJO 的 toString()方法打印其屬性值,便于排 查問(wèn)題。
白話(huà):
這里還有一個(gè)大坑,寫(xiě)toString的時(shí)候要保證不會(huì)發(fā)生NPE,有的時(shí)候toString調(diào)用實(shí)例變量的toString,實(shí)例變量由于某些原因?yàn)閚ull,導(dǎo)致NPE,代碼沒(méi)有處理好就終止,這個(gè)問(wèn)題坑了好多次。
13.【推薦】使用索引訪(fǎng)問(wèn)用 String 的 split 方法得到的數(shù)組時(shí),需做最后一個(gè)分隔符后有無(wú)內(nèi)容的檢查,否則會(huì)有拋 IndexOutOfBoundsException 的風(fēng)險(xiǎn)。
說(shuō)明:
- String str = "a,b,c,,";
- String[] ary = str.split(","); //預(yù)期大于 3,結(jié)果是 3
- System.out.println(ary.length);
白話(huà):
編程要留心眼,任何不確定的地方都要判斷、處理,否則掉到坑里了自己爬出來(lái)很費(fèi)勁。
Java編程判空的思想要實(shí)施縈繞在每個(gè)開(kāi)發(fā)人員的腦海里。
14.【推薦】當(dāng)一個(gè)類(lèi)有多個(gè)構(gòu)造方法,或者多個(gè)同名方法,這些方法應(yīng)該按順序放置在一起, 便于閱讀。
白話(huà):
這規(guī)范說(shuō)的咋就和我的習(xí)慣一模一樣呢!
1.【推薦】 類(lèi)內(nèi)方法定義順序依次是: 公有方法或保護(hù)方法 > 私有方法 > getter/setter
方法。
說(shuō)明: 公有方法是類(lèi)的調(diào)用者和維護(hù)者最關(guān)心的方法,首屏展示最好; 保護(hù)方法雖然只是子類(lèi) 關(guān)心,也可能是“模板設(shè)計(jì)模式”下的核心方法; 而私有方法外部一般不需要特別關(guān)心,是一個(gè)黑盒實(shí)現(xiàn); 因?yàn)榉椒ㄐ畔r(jià)值較低,所有 Service 和 DAO 的 getter/setter 方法放在類(lèi)體最 后。
白話(huà):
我推薦把一套邏輯的共有方法、保護(hù)方法、私有方法放在一起,所有g(shù)etter/setter放在最后,這樣感覺(jué)更有邏輯!
2.【推薦】setter 方法中,參數(shù)名稱(chēng)與類(lèi)成員變量名稱(chēng)一致,this.成員名 = 參數(shù)名。在
getter/setter 方法中,盡量不要增加業(yè)務(wù)邏輯,增加排查問(wèn)題的難度。
反例:
- public Integer getData() {
- if (true) {
- return data + 100;
- } else {
- return data - 100; }
- }
白話(huà):
雙手贊成。
3.【推薦】循環(huán)體內(nèi),字符串的連接方式,使用 StringBuilder 的 append 方法進(jìn)行擴(kuò)展。
反例:
- String str = "start";
- for (int I = 0; I < 100; i++) {
- str = str + "hello";
- }
說(shuō)明: 反編譯出的字節(jié)碼文件顯示每次循環(huán)都會(huì) new 出一個(gè) StringBuilder 對(duì)象,然后進(jìn)行 append 操作,最后通過(guò) toString 方法返回 String 對(duì)象,造成內(nèi)存資源浪費(fèi)。
白話(huà):
一定使用StringBuilder,不要使用StringBuffer,StringBuffer是線(xiàn)程安全的,太重。
我就一直想不明白Java編譯器為什么不做個(gè)優(yōu)化呢?
4.【推薦】下列情況,聲明成 final 會(huì)更有提示性:
1) 不需要重新賦值的變量,包括類(lèi)屬性、局部變量。
2) 對(duì)象參數(shù)前加final,表示不允許修改引用的指向。
3) 類(lèi)方法確定不允許被重寫(xiě)。
白話(huà):
盡量多使用final關(guān)鍵字,保證編譯器的校驗(yàn)機(jī)制起作用,也體現(xiàn)了“契約式編程”的思想。
5.【推薦】慎用 Object 的 clone 方法來(lái)拷貝對(duì)象。
說(shuō)明: 對(duì)象的 clone 方法默認(rèn)是淺拷貝,若想實(shí)現(xiàn)深拷貝需要重寫(xiě) clone 方法實(shí)現(xiàn)屬性對(duì)象的拷貝。
白話(huà):
最好是使用構(gòu)造函數(shù)來(lái)重新構(gòu)造對(duì)象,使用clone淺拷貝的時(shí)候,對(duì)象引用關(guān)系可能很復(fù)雜,不直觀(guān),不好理解。
6.【推薦】類(lèi)成員與方法訪(fǎng)問(wèn)控制從嚴(yán):
1) 如果不允許外部直接通過(guò)new來(lái)創(chuàng)建對(duì)象,那么構(gòu)造方法必須是private。
2) 工具類(lèi)不允許有public或default構(gòu)造方法。
3) 類(lèi)非static成員變量并且與子類(lèi)共享,必須是protected。
4) 類(lèi)非static成員變量并且僅在本類(lèi)使用,必須是private。
5) 類(lèi)static成員變量如果僅在本類(lèi)使用,必須是private。
6) 若是static成員變量,必須考慮是否為final。
7) 類(lèi)成員方法只供類(lèi)內(nèi)部調(diào)用,必須是private。
8) 類(lèi)成員方法只對(duì)繼承類(lèi)公開(kāi),那么限制為protected。
說(shuō)明: 任何類(lèi)、方法、參數(shù)、變量,嚴(yán)控訪(fǎng)問(wèn)范圍。過(guò)寬泛的訪(fǎng)問(wèn)范圍,不利于模塊解耦。
思考: 如果是一個(gè) private 的方法,想刪除就刪除,可是一個(gè) public 的 Service 方法,或者一個(gè) public 的成員變量,刪除一下,不得手心冒點(diǎn)汗嗎?變量像自己的小孩,盡量在自己的視 線(xiàn)內(nèi),變量作用域太大,如果無(wú)限制的到處跑,那么你會(huì)擔(dān)心的。
白話(huà):
沒(méi)什么好說(shuō)的,兩個(gè)詞,高內(nèi)聚,低耦合,功能模塊閉包,哦,是三個(gè)詞。
5 集合處理
1.【強(qiáng)制】關(guān)于 hashCode 和 equals 的處理,遵循如下規(guī)則:
1) 只要重寫(xiě)equals,就必須重寫(xiě)hashCode。
2) 因?yàn)镾et存儲(chǔ)的是不重復(fù)的對(duì)象,依據(jù)hashCode和equals進(jìn)行判斷,所以Set存儲(chǔ)的對(duì)象必須重寫(xiě)這兩個(gè)方法。
3) 如果自定義對(duì)象做為Map的鍵,那么必須重寫(xiě)hashCode和equals。
說(shuō)明: String 重寫(xiě)了 hashCode 和 equals 方法,所以我們可以非常愉快地使用 String 對(duì)象作為 key 來(lái)使用。
白話(huà):
Hash是個(gè)永恒的話(huà)題,大家可以看下times33和Murmurhash算法。
2.【強(qiáng)制】ArrayList的subList結(jié)果不可強(qiáng)轉(zhuǎn)成ArrayList,否則會(huì)拋出ClassCastException
異常: java.util.RandomAccessSubList cannot be cast to java.util.ArrayList ;
說(shuō)明: subList 返回的是 ArrayList 的內(nèi)部類(lèi) SubList,并不是 ArrayList ,而是 ArrayList 的一個(gè)視圖,對(duì)于SubList子列表的所有操作最終會(huì)反映到原列表上。
白話(huà):
這種問(wèn)題本來(lái)測(cè)試可以測(cè)試到,但是開(kāi)發(fā)永遠(yuǎn)都不要有依賴(lài)測(cè)試的想法,一切靠自己,當(dāng)然我們的測(cè)試人員都是很靠譜的。
3.【強(qiáng)制】 在 subList 場(chǎng)景中,高度注意對(duì)原集合元素個(gè)數(shù)的修改,會(huì)導(dǎo)致子列表的遍歷、增 加、刪除均產(chǎn)生ConcurrentModificationException 異常。
白話(huà):
如果一定要更改子列表,重新構(gòu)造新的ArrayList,使用
- public ArrayList(Collection<? extends E> c)
4.【強(qiáng)制】使用集合轉(zhuǎn)數(shù)組的方法,必須使用集合的toArray(T[] array),傳入的是類(lèi)型完全 一樣的數(shù)組,大小就是 list.size()。
說(shuō)明: 使用 toArray 帶參方法,入?yún)⒎峙涞臄?shù)組空間不夠大時(shí),toArray 方法內(nèi)部將重新分配內(nèi)存空間,并返回新數(shù)組地址; 如果數(shù)組元素大于實(shí)際所需,下標(biāo)為[ list.size() ]的數(shù)組元素將被置為 null,其它數(shù)組元素保持原值,因此最好將方法入?yún)?shù)組大小定義與集合元素個(gè)數(shù)一致。
正例:
- List<String> list = new ArrayList<String>(2); list.add("guan");
- list.add("bao");
- String[] array = new String[list.size()];
- array = list.toArray(array);
反例: 直接使用 toArray 無(wú)參方法存在問(wèn)題,此方法返回值只能是 Object[]類(lèi),若強(qiáng)轉(zhuǎn)其它類(lèi)型數(shù)組將出現(xiàn) ClassCastException 錯(cuò)誤。
白話(huà):
搞不懂Java編譯器為什么不做優(yōu)化,人用邏輯能推導(dǎo)的,程序一定可以自動(dòng)實(shí)現(xiàn)。
5.【強(qiáng)制】使用工具類(lèi) Arrays.asList()把數(shù)組轉(zhuǎn)換成集合時(shí),不能使用其修改集合相關(guān)的方 法,它的 add/remove/clear 方法會(huì)拋出 UnsupportedOperationException 異常。
說(shuō)明: asList 的返回對(duì)象是一個(gè) Arrays 內(nèi)部類(lèi),并沒(méi)有實(shí)現(xiàn)集合的修改方法。Arrays.asList 體現(xiàn)的是適配器模式,只是轉(zhuǎn)換接口,后臺(tái)的數(shù)據(jù)仍是數(shù)組。
- String[] str = new String[] { "a", "b" };
- List list = Arrays.asList(str);
第一種情況: list.add("c"); 運(yùn)行時(shí)異常。
第二種情況: str[0] = "gujin"; 那么list.get(0)也會(huì)隨之修改。
白話(huà):
如果需要對(duì)asList返回的List做更改,可以構(gòu)造新的ArrayList,使用public ArrayList(Collection<? extends E> c)構(gòu)造器。
6.【強(qiáng)制】泛型通配符<? extends T>來(lái)接收返回的數(shù)據(jù),此寫(xiě)法的泛型集合不能使用add方 法,而<? super T>不能使用get方法,做為接口調(diào)用賦值時(shí)易出錯(cuò)。
說(shuō)明: 擴(kuò)展說(shuō)一下PECS(Producer Extends Consumer Super)原則: 1)頻繁往外讀取內(nèi)容的,適合用上界 Extends。 2)經(jīng)常往里插入的,適合用下界 Super。
白話(huà):
<? extends T>, ? 必須是T或T的子類(lèi)
集合寫(xiě)(add): 因?yàn)椴荒艽_定集合實(shí)例化時(shí)用的是T或T的子類(lèi),所以沒(méi)有辦法寫(xiě)。例如:List<? extends Number> foo = new ArrayList<Number/Integer/Double>(),你不能add Number,因?yàn)橐部赡苁荌nteger或Double的List, 同理也不能add Integer或Double,即,extends T, 不能集合add。
集合讀(get): 只能讀出T類(lèi)型的數(shù)據(jù)。
<? super T>, ? 必須是T或T的父類(lèi)
集合寫(xiě)(add): 可以add T或T的子類(lèi)。
集合讀(get): 不能確定從集合里讀出的是哪個(gè)類(lèi)型(可能是T也可能是T的父類(lèi),或者Object),所以沒(méi)有辦法使用get。例如:List<? super Integer> foo3 = new ArrayList<Integer/Number/Object>(); 只能保證get出來(lái)是Object。
下面是示例,test1和test2在編譯時(shí)都有錯(cuò)誤提示。
- package com.robert.javaspec;
- import java.util.LinkedList;
- import java.util.List;
- /**
- * Created by WangMeng on 2017-04-13.
- * FIX ME
- */
- public class Main {
- public static void main(String[] args) {
- }
- public void test1(){
- List<? extends A> childofa=new LinkedList<>();
- B b=new B();
- A a=new A();
- childofa.add(a);
- childofa.add(b);
- A ta= childofa.get(0);
- }
- public void test2(){
- List<? super B> superOfb = new LinkedList<>();
- B b = new B();
- A a = new A();
- superOfb.add(a);
- superOfb.add(b);
- A ta = superOfb.get(0);
- B tb = superOfb.get(0);
- }
- }
- class A {
- @Override
- public String toString() {
- return "A";
- }
- }
- class B extends A {
- @Override
- public String toString() {
- return "B";
- }
- }
7.【強(qiáng)制】不要在 foreach 循環(huán)里進(jìn)行元素的 remove/add 操作。remove 元素請(qǐng)使用 Iterator 方式,如果并發(fā)操作,需要對(duì) Iterator 對(duì)象加鎖。
反例:
- List<String> a = new ArrayList<String>();
- a.add("1");
- a.add("2");
- for (String temp : a) {
- if ("1".equals(temp)) {
- a.remove(temp);
- }
- }
說(shuō)明: 以上代碼的執(zhí)行結(jié)果肯定會(huì)出乎大家的意料,那么試一下把“1”換成“2”,會(huì)是同樣的
結(jié)果嗎?
正例:
- Iterator<String> it = a.iterator();
- while (it.hasNext()) {
- String temp = it.next();
- if (刪除元素的條件) {
- it.remove();
- }
- }
白話(huà):
修改一定要使用Iterator。
反例中改成2,拋出ConcurrentModificationException,因?yàn)?是數(shù)組的結(jié)束邊界。
8.【強(qiáng)制】 在 JDK7 版本及以上,Comparator 要滿(mǎn)足如下三個(gè)條件,不然 Arrays.sort, Collections.sort 會(huì)報(bào) IllegalArgumentException 異常。
說(shuō)明:
- 1) x,y的比較結(jié)果和y,x的比較結(jié)果相反。
- 2) x>y,y>z,則x>z。
- 3) x=y,則x,z比較結(jié)果和y,z比較結(jié)果相同。
反例: 下例中沒(méi)有處理相等的情況,實(shí)際使用中可能會(huì)出現(xiàn)異常:
- new Comparator<Student>() {
- @Override
- public int compare(Student o1, Student o2) {
- return o1.getId() > o2.getId() ? 1 : -1;
- }
- }
白話(huà):
除非邏輯混亂,否則這些條件都能滿(mǎn)足。
9.【推薦】集合初始化時(shí),盡量指定集合初始值大小。
說(shuō)明: ArrayList盡量使用ArrayList(int initialCapacity) 初始化。
白話(huà):
預(yù)估數(shù)組大小,能夠提高程序效率,寫(xiě)代碼的時(shí)候腦袋里面要有運(yùn)行的思想。
想了解性能和容量評(píng)估,請(qǐng)參考互聯(lián)網(wǎng)性能與容量評(píng)估的方法論和典型案例。
10.【推薦】使用 entrySet 遍歷 Map 類(lèi)集合 KV,而不是 keySet 方式進(jìn)行遍歷。
說(shuō)明: keySet 其實(shí)是遍歷了 2 次,一次是轉(zhuǎn)為 Iterator 對(duì)象,另一次是從 hashMap 中取出 key 所對(duì)應(yīng)的 value。而 entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.foreach 方法。
正例: values()返回的是 V 值集合,是一個(gè) list 集合對(duì)象;keySet()返回的是 K 值集合,是一個(gè) Set 集合對(duì)象; entrySet()返回的是 K-V 值組合集合。
白話(huà):
寫(xiě)代碼其實(shí)就是在程序員腦袋里執(zhí)行代碼的過(guò)程,直覺(jué)就是兩次肯定不如一次做完事更快。
11.【推薦】高度注意 Map 類(lèi)集合 K/V 能不能存儲(chǔ) null 值的情況,如下表格:
集合對(duì)照表
反例: 由于 HashMap 的干擾,很多人認(rèn)為 ConcurrentHashMap 是可以置入 null 值,注意存儲(chǔ) null 值時(shí)會(huì)拋出 NPE 異常。
白話(huà):
存儲(chǔ)null值場(chǎng)景不多,在防止緩存穿透的情況下,有的時(shí)候會(huì)緩存null key
12.【參考】合理利用好集合的有序性(sort)和穩(wěn)定性(order),避免集合的無(wú)序性(unsort)和 不穩(wěn)定性(unorder)帶來(lái)的負(fù)面影響。
說(shuō)明: 有序性是指遍歷的結(jié)果是按某種比較規(guī)則依次排列的。穩(wěn)定性指集合每次遍歷的元素次 序是一定的。如:ArrayList 是 order/unsort;HashMap 是 unorder/unsort;TreeSet 是 order/sort。
白話(huà):
大體上同意,但是對(duì)于HashMap理論上是無(wú)序的沒(méi)有問(wèn)題,我做了個(gè)試驗(yàn),每次輸出都是穩(wěn)定的。
數(shù)值:
- HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
- map.put(3, 3);
- map.put(1, 1);
- map.put(2, 2);
- map.put(4, 4);
- for (Entry<Integer, Integer> entry : map.entrySet()) {
- System.out.println(entry.getKey());
- }
事實(shí)證明,每次輸出也是1、2、3、4,有序并且穩(wěn)定的。
字符串值:
- HashMap<String, String> map = new HashMap<String, String>();
- map.put("3000", "3");
- map.put("1000", "1");
- map.put("2000", "2");
- map.put("4000", "4");
- for (Entry<Integer, Integer> entry : map.entrySet()) {
- System.out.println(entry.getKey());
- }
事實(shí)證明,每次輸出也是4000、1000、2000、3000,無(wú)序但是穩(wěn)定的。
與阿里專(zhuān)家咨詢(xún),這里HashMap不穩(wěn)定性是指rehash后輸出順序則會(huì)變化。
13.【參考】利用 Set 元素唯一的特性,可以快速對(duì)一個(gè)集合進(jìn)行去重操作,避免使用 List 的 contains 方法進(jìn)行遍歷、對(duì)比、去重操作。
白話(huà):
如果不需要精確去重,參考布隆過(guò)濾器(Bloom Filter)。
6 并發(fā)處理
1.【強(qiáng)制】獲取單例對(duì)象需要保證線(xiàn)程安全,其中的方法也要保證線(xiàn)程安全。
說(shuō)明: 資源驅(qū)動(dòng)類(lèi)、工具類(lèi)、單例工廠(chǎng)類(lèi)都需要注意。
白話(huà):
如果延遲加載實(shí)現(xiàn)的單例需要并發(fā)控制;如果初始化的時(shí)候new單例對(duì)象,本身是線(xiàn)程安全的,取得實(shí)例方法不需要同步。
2.【強(qiáng)制】創(chuàng)建線(xiàn)程或線(xiàn)程池時(shí)請(qǐng)指定有意義的線(xiàn)程名稱(chēng),方便出錯(cuò)時(shí)回溯。
正例:
- public class TimerTaskThread extends Thread {
- public TimerTaskThread() {
- super.setName("TimerTaskThread");
- ...
- }
- }
白話(huà):
寫(xiě)代碼的時(shí)候就要想到查bug的時(shí)候要用到什么信息,然后決定如何命名、打印日志等。
3.【強(qiáng)制】線(xiàn)程資源必須通過(guò)線(xiàn)程池提供,不允許在應(yīng)用中自行顯式創(chuàng)建線(xiàn)程。
說(shuō)明: 使用線(xiàn)程池的好處是減少在創(chuàng)建和銷(xiāo)毀線(xiàn)程上所花的時(shí)間以及系統(tǒng)資源的開(kāi)銷(xiāo),解決資 源不足的問(wèn)題。如果不使用線(xiàn)程池,有可能造成系統(tǒng)創(chuàng)建大量同類(lèi)線(xiàn)程而導(dǎo)致消耗完內(nèi)存或者 “過(guò)度切換”的問(wèn)題。
白話(huà):
一個(gè)是使用線(xiàn)程池緩存線(xiàn)程可以提高效率,另外線(xiàn)程池幫我們做了管理線(xiàn)程的事情,提供了優(yōu)雅關(guān)機(jī)、interrupt等待IO的線(xiàn)程,飽和策略等功能。
4.【強(qiáng)制】線(xiàn)程池不允許使用 Executors 去創(chuàng)建,而是通過(guò) ThreadPoolExecutor 的方式,這樣的處理方式讓寫(xiě)的同學(xué)更加明確線(xiàn)程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)。
說(shuō)明: Executors 返回的線(xiàn)程池對(duì)象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool: 允許的請(qǐng)求隊(duì)列長(zhǎng)度為 Integer.MAX_VALUE,可能會(huì)堆積大量的請(qǐng)求,從而導(dǎo)致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool: 允許的創(chuàng)建線(xiàn)程數(shù)量為 Integer.MAX_VALUE,可能會(huì)創(chuàng)建大量的線(xiàn)程,從而導(dǎo)致 OOM。
白話(huà):
線(xiàn)程池如果沒(méi)有限制最大數(shù)量,線(xiàn)程池?fù)伍_(kāi)的時(shí)候,由于內(nèi)存不夠或者系統(tǒng)配置的最大線(xiàn)程數(shù)超出,都會(huì)產(chǎn)生oom: unalbe to create native thread。
一個(gè)組件的核心參數(shù)最好要顯式的傳入,不要默認(rèn),就像你交給屬下一個(gè)任務(wù),任務(wù)的目標(biāo)、原則、時(shí)間點(diǎn)、邊界都要明確,不能模糊處理一樣,免得扯皮。
5.【強(qiáng)制】SimpleDateFormat 是線(xiàn)程不安全的類(lèi),一般不要定義為static變量,如果定義為
static,必須加鎖,或者使用 DateUtils 工具類(lèi)。
正例: 注意線(xiàn)程安全,使用 DateUtils。亦推薦如下處理:
- @Override
- protected DateFormat initialValue() {
- return new SimpleDateFormat("yyyy-MM-dd");
- }
說(shuō)明: 如果是 JDK8 的應(yīng)用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替 Simpledateformatter,官方給出的解釋: simple beautiful strong immutable thread-safe。
白話(huà):
記住,打死你,我也不會(huì)把SimpleDateFormat共享到類(lèi)中。
【強(qiáng)制】高并發(fā)時(shí),同步調(diào)用應(yīng)該去考量鎖的性能損耗。能用無(wú)鎖數(shù)據(jù)結(jié)構(gòu),就不要用鎖; 能鎖區(qū)塊,就不要鎖整個(gè)方法體; 能用對(duì)象鎖,就不要用類(lèi)鎖。
白話(huà):
優(yōu)先無(wú)鎖,不用鎖能解決的一定不要用鎖,即使用鎖也要控制粒度,越細(xì)越好。
7.【強(qiáng)制】對(duì)多個(gè)資源、數(shù)據(jù)庫(kù)表、對(duì)象同時(shí)加鎖時(shí),需要保持一致的加鎖順序,否則可能會(huì)造 成死鎖。
說(shuō)明: 線(xiàn)程一需要對(duì)表 A、B、C 依次全部加鎖后才可以進(jìn)行更新操作,那么線(xiàn)程二的加鎖順序也必須是 A、B、C,否則可能出現(xiàn)死鎖。
白話(huà):
解決死鎖的方法:按順序鎖資源、超時(shí)、優(yōu)先級(jí)、死鎖檢測(cè)等。
可參考哲學(xué)家進(jìn)餐問(wèn)題學(xué)習(xí)更深入的并發(fā)機(jī)制。
8.【強(qiáng)制】并發(fā)修改同一記錄時(shí),避免更新丟失,需要加鎖。要么在應(yīng)用層加鎖,要么在緩存加 鎖,要么在數(shù)據(jù)庫(kù)層使用樂(lè)觀(guān)鎖,使用 version 作為更新依據(jù)。
說(shuō)明: 如果每次訪(fǎng)問(wèn)沖突概率小于 20%,推薦使用樂(lè)觀(guān)鎖,否則使用悲觀(guān)鎖。樂(lè)觀(guān)鎖的重試次 數(shù)不得小于 3 次。
白話(huà):
狀態(tài)流轉(zhuǎn)、維護(hù)可用余額等最好直接利用數(shù)據(jù)庫(kù)的行級(jí)鎖,不需要顯式的加鎖。
9.【強(qiáng)制】多線(xiàn)程并行處理定時(shí)任務(wù)時(shí),Timer 運(yùn)行多個(gè) TimerTask 時(shí),只要其中之一沒(méi)有捕獲拋出的異常,其它任務(wù)便會(huì)自動(dòng)終止運(yùn)行,使用 ScheduledExecutorService 則沒(méi)有這個(gè)問(wèn)題。
白話(huà):
線(xiàn)程執(zhí)行體、任務(wù)最上層等一定要抓住Throwable并進(jìn)行相應(yīng)的處理,否則會(huì)使線(xiàn)程終止。
10.【推薦】使用 CountDownLatch 進(jìn)行異步轉(zhuǎn)同步操作,每個(gè)線(xiàn)程退出前必須調(diào)用 countDown 方法,線(xiàn)程執(zhí)行代碼注意 catch 異常,確保 countDown 方法可以執(zhí)行,避免主線(xiàn)程無(wú)法執(zhí)行至 await 方法,直到超時(shí)才返回結(jié)果。
說(shuō)明: 注意,子線(xiàn)程拋出異常堆棧,不能在主線(xiàn)程 try-catch 到。
白話(huà):
請(qǐng)?jiān)趖ry...finally語(yǔ)句里執(zhí)行countDown方法,與關(guān)閉資源類(lèi)似。
11.【推薦】避免 Random 實(shí)例被多線(xiàn)程使用,雖然共享該實(shí)例是線(xiàn)程安全的,但會(huì)因競(jìng)爭(zhēng)同一 seed 導(dǎo)致的性能下降。
說(shuō)明: Random 實(shí)例包括 java.util.Random 的實(shí)例或者 Math.random()實(shí)例。
正例: 在 JDK7 之后,可以直接使用 API ThreadLocalRandom,在 JDK7 之前,可以做到每個(gè)線(xiàn)程一個(gè)實(shí)例。
白話(huà):
可以把Random放在ThreadLocal里,只在本線(xiàn)程中使用。
12.【推薦】在并發(fā)場(chǎng)景下,通過(guò)雙重檢查鎖(double-checked locking)實(shí)現(xiàn)延遲初始化的優(yōu) 化問(wèn)題隱患(可參考 The "Double-Checked Locking is Broken" Declaration),推薦問(wèn) 題解決方案中較為簡(jiǎn)單一種(適用于 JDK5 及以上版本),將目標(biāo)屬性聲明為 volatile 型。
反例:
- class Foo {
- private Helper helper = null;
- public Helper getHelper() {
- if (helper == null)
- synchronized(this) {
- if (helper == null)
- helper = new Helper();
- }
- return helper;
- }
- // other functions and members...
- }
白話(huà):
網(wǎng)上對(duì)雙檢鎖有N多討論,這里很負(fù)責(zé)任的告訴大家,只要不是特別老的JDK版本(1.4以下),雙檢鎖是沒(méi)問(wèn)題的。
13.【參考】volatile 解決多線(xiàn)程內(nèi)存不可見(jiàn)問(wèn)題。對(duì)于一寫(xiě)多讀,是可以解決變量同步問(wèn)題, 但是如果多寫(xiě),同樣無(wú)法解決線(xiàn)程安全問(wèn)題。如果是 count++操作,使用如下類(lèi)實(shí)現(xiàn): AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推薦使用 LongAdder 對(duì)象,比 AtomicLong 性能更好(減少樂(lè)觀(guān)鎖的重試次數(shù))。
白話(huà):
volatile只有內(nèi)存可見(jiàn)性語(yǔ)義,synchronized有互斥語(yǔ)義,一寫(xiě)多讀使用volatile就可以,多寫(xiě)就必須使用synchronized,fetch-mod-get也必須使用synchronized。
14.【參考】 HashMap 在容量不夠進(jìn)行 resize 時(shí)由于高并發(fā)可能出現(xiàn)死鏈,導(dǎo)致 CPU 飆升,在開(kāi)發(fā)過(guò)程中注意規(guī)避此風(fēng)險(xiǎn)。
白話(huà):
開(kāi)發(fā)程序的時(shí)候要預(yù)估使用量,根據(jù)使用量來(lái)設(shè)置初始值。
resize需要重建hash表,嚴(yán)重影響性能,會(huì)讓程序產(chǎn)生長(zhǎng)尾的響應(yīng)時(shí)間。
15.【參考】ThreadLocal 無(wú)法解決共享對(duì)象的更新問(wèn)題,ThreadLocal 對(duì)象建議使用 static 修飾。這個(gè)變量是針對(duì)一個(gè)線(xiàn)程內(nèi)所有操作共有的,所以設(shè)置為靜態(tài)變量,所有此類(lèi)實(shí)例共享此靜態(tài)變量 ,也就是說(shuō)在類(lèi)第一次被使用時(shí)裝載,只分配一塊存儲(chǔ)空間,所有此類(lèi)的對(duì)象(只 要是這個(gè)線(xiàn)程內(nèi)定義的)都可以操控這個(gè)變量。
白話(huà):
ThreadLocal實(shí)際上是一個(gè)從線(xiàn)程ID到變量的Map,每次取得ThreadLocal變量,實(shí)際上是先取得當(dāng)前線(xiàn)程ID,再用當(dāng)前線(xiàn)程ID取得關(guān)聯(lián)的變量。
ThreadLocal使用了WeakHashMap,在key被回收的時(shí)候,value也被回收了,不用擔(dān)心內(nèi)存泄露。
7 控制語(yǔ)句
1.【強(qiáng)制】在一個(gè) switch 塊內(nèi),每個(gè) case 要么通過(guò) break/return 等來(lái)終止,要么注釋說(shuō)明程序?qū)⒗^續(xù)執(zhí)行到哪一個(gè) case 為止;在一個(gè) switch 塊內(nèi),都必須包含一個(gè) default 語(yǔ)句并且放在最后,即使它什么代碼也沒(méi)有。
白話(huà):
最好每個(gè)case都用break結(jié)束,不要組合幾個(gè)分支到一個(gè)邏輯,太不直觀(guān)。
2.【強(qiáng)制】在 if/else/for/while/do 語(yǔ)句中必須使用大括號(hào),即使只有一行代碼,避免使用 下面的形式:if (condition) statements;
白話(huà):
這條有歧義,個(gè)人認(rèn)為有的時(shí)候就一行語(yǔ)句不加也可以。
3.【推薦】推薦盡量少用 else, if-else 的方式可以改寫(xiě)成:
- if (condition) { ...
- return obj;
- }
- // 接著寫(xiě) else 的業(yè)務(wù)邏輯代碼;
說(shuō)明: 如果非得使用if()...else if()...else...方式表達(dá)邏輯,【強(qiáng)制】請(qǐng)勿超過(guò)3層, 超過(guò)請(qǐng)使用狀態(tài)設(shè)計(jì)模式。
正例: 邏輯上超過(guò) 3 層的 if-else 代碼可以使用衛(wèi)語(yǔ)句,或者狀態(tài)模式來(lái)實(shí)現(xiàn)。
白話(huà):
朋友說(shuō)超過(guò)三層考慮狀態(tài)設(shè)計(jì)模式也不完全正確,大概可以理解為多層的邏輯嵌套不是好的代碼風(fēng)格,需要使用對(duì)應(yīng)的重構(gòu)方法做出優(yōu)化,而每種壞味都有對(duì)應(yīng)的優(yōu)化方法和步驟,以及優(yōu)缺點(diǎn)限制條件。
寫(xiě)程序一定要遵守紅花綠葉原則,主邏輯放在主方法中,這是紅花,子邏輯封裝成小方法調(diào)用,這是綠葉,不要把不同層次的邏輯寫(xiě)在一個(gè)大方法體里,很難理解,就像綠葉把紅花擋住了,誰(shuí)還能看到。舉例說(shuō)明:
- public void handleProcess() {
- // 骨架邏輯
- validate();
- doProcess();
- declareResource();
- }
普及一下,如下類(lèi)似排比句的代碼就是衛(wèi)語(yǔ)句,以前每天都這么寫(xiě)但是還真是剛剛知道這叫衛(wèi)語(yǔ)句:)
- public double getPayAmount() {
- if (isDead()) return deadPayAmount();
- if (isSeparated()) return separatedPayAmount();
- if (isRetired()) return retiredPayAmount();
- return normalPayAmount();
- }
不提倡的寫(xiě)法:
- public double getPayAmount() {
- if (isDead())
- return deadPayAmount();
- else if (isSeparated())
- return separatedPayAmount();
- else if (isRetired())
- return retiredPayAmount();
- else
- return normalPayAmount();
- }
4.【推薦】除常用方法(如 getXxx/isXxx)等外,不要在條件判斷中執(zhí)行其它復(fù)雜的語(yǔ)句,將復(fù) 雜邏輯判斷的結(jié)果賦值給一個(gè)有意義的布爾變量名,以提高可讀性。
說(shuō)明: 很多 if 語(yǔ)句內(nèi)的邏輯相當(dāng)復(fù)雜,閱讀者需要分析條件表達(dá)式的最終結(jié)果,才能明確什么 樣的條件執(zhí)行什么樣的語(yǔ)句,那么,如果閱讀者分析邏輯表達(dá)式錯(cuò)誤呢?
正例:
//偽代碼如下
- //偽代碼如下
- boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
- if (existed) {
- ...
- }
反例:
- if ((file.open(fileName, "w") != null) && (...) || (...)) { ...}
白話(huà):
這個(gè)反例真的經(jīng)常見(jiàn)到,寫(xiě)這個(gè)代碼的人自己不覺(jué)得這樣很難看嗎?
5.【推薦】循環(huán)體中的語(yǔ)句要考量性能,以下操作盡量移至循環(huán)體外處理,如定義對(duì)象、變量、
獲取數(shù)據(jù)庫(kù)連接,進(jìn)行不必要的 try-catch 操作(這個(gè) try-catch 是否可以移至循環(huán)體外)。
白話(huà):
切記,循環(huán)體內(nèi)盡量不要獲取資源、不要處理異常。
6.【推薦】接口入?yún)⒈Wo(hù),這種場(chǎng)景常見(jiàn)的是用于做批量操作的接口。
白話(huà):
用白話(huà)說(shuō),就是控制批量參數(shù)的數(shù)量,一次不能太多,否則內(nèi)存溢出。
7.【參考】方法中需要進(jìn)行參數(shù)校驗(yàn)的場(chǎng)景:
1) 調(diào)用頻次低的方法。
2) 執(zhí)行時(shí)間開(kāi)銷(xiāo)很大的方法,參數(shù)校驗(yàn)時(shí)間幾乎可以忽略不計(jì),但如果因?yàn)閰?shù)錯(cuò)誤導(dǎo)致
中間執(zhí)行回退,或者錯(cuò)誤,那得不償失。
3) 需要極高穩(wěn)定性和可用性的方法。
4) 對(duì)外提供的開(kāi)放接口,不管是RPC/API/HTTP接口。
5) 敏感權(quán)限入口。
白話(huà):
在這個(gè)框框內(nèi),根據(jù)業(yè)務(wù)適當(dāng)調(diào)整是可以的。
8.【參考】方法中不需要參數(shù)校驗(yàn)的場(chǎng)景:
1) 極有可能被循環(huán)調(diào)用的方法,不建議對(duì)參數(shù)進(jìn)行校驗(yàn)。但在方法說(shuō)明里必須注明外部參
數(shù)檢查要求。
2) 底層的方法調(diào)用頻度都比較高,一般不校驗(yàn)。畢竟是像純凈水過(guò)濾的最后一道,參數(shù)錯(cuò)
誤不太可能到底層才會(huì)暴露問(wèn)題。一般 DAO 層與 Service 層都在同一個(gè)應(yīng)用中,部署在同一 臺(tái)服務(wù)器中,所以 DAO 的參數(shù)校驗(yàn),可以省略。
3) 被聲明成private只會(huì)被自己代碼所調(diào)用的方法,如果能夠確定調(diào)用方法的代碼傳入?yún)?shù)已經(jīng)做過(guò)檢查或者肯定不會(huì)有問(wèn)題,此時(shí)可以不校驗(yàn)參數(shù)。
白話(huà):
在這個(gè)框框呢,根據(jù)業(yè)務(wù)適當(dāng)調(diào)整是可以的。
8 注釋規(guī)約
1.【強(qiáng)制】類(lèi)、類(lèi)屬性、類(lèi)方法的注釋必須使用 Javadoc 規(guī)范,使用/*內(nèi)容/格式,不得使用 //xxx 方式。
說(shuō)明:在 IDE 編輯窗口中,Javadoc 方式會(huì)提示相關(guān)注釋?zhuān)?Javadoc 可以正確輸出相應(yīng)注 釋; 在 IDE 中,工程調(diào)用方法時(shí),不進(jìn)入方法即可懸浮提示方法、參數(shù)、返回值的意義,提高閱讀效率。
白話(huà):
開(kāi)發(fā)環(huán)境編輯器可保證這一條。
2.【強(qiáng)制】所有的抽象方法(包括接口中的方法)必須要用 Javadoc 注釋、除了返回值、參數(shù)、 異常說(shuō)明外,還必須指出該方法做什么事情,實(shí)現(xiàn)什么功能。
說(shuō)明: 對(duì)子類(lèi)的實(shí)現(xiàn)要求,或者調(diào)用注意事項(xiàng),請(qǐng)一并說(shuō)明。
白話(huà):
文檔還要涵蓋校驗(yàn),線(xiàn)程安全等,重要方法要給出調(diào)用示例。
3.【強(qiáng)制】所有的類(lèi)都必須添加創(chuàng)建者信息。
白話(huà):
做好事要留名,要不誰(shuí)知道是你干的,方法可參考雷鋒,雷鋒每次做好事不留名,每次時(shí)間地點(diǎn)都被記下來(lái)了。
4.【強(qiáng)制】方法內(nèi)部單行注釋?zhuān)诒蛔⑨屨Z(yǔ)句上方另起一行,使用//注釋。方法內(nèi)部多行注釋
使用/ /注釋?zhuān)⒁馀c代碼對(duì)齊。
白話(huà):
關(guān)鍵的地方做注釋?zhuān)膊灰啵a最好是自文檔化,看代碼就能讓人理解,這是境界。
5.【強(qiáng)制】所有的枚舉類(lèi)型字段必須要有注釋?zhuān)f(shuō)明每個(gè)數(shù)據(jù)項(xiàng)的用途。
白話(huà):
枚舉是個(gè)非常重要的類(lèi)型,業(yè)務(wù)代碼中大量存在,注釋是必須的。
6.【推薦】與其“半吊子”英文來(lái)注釋?zhuān)蝗缬弥形淖⑨尠褑?wèn)題說(shuō)清楚。專(zhuān)有名詞與關(guān)鍵字保持英文原文即可。
反例:“TCP 連接超時(shí)”解釋成“傳輸控制協(xié)議連接超時(shí)”,理解反而費(fèi)腦筋。
白話(huà):
還是那句話(huà),脫了褲子放屁還是累,又沒(méi)人感激你翻譯了。
7.【推薦】代碼修改的同時(shí),注釋也要進(jìn)行相應(yīng)的修改,尤其是參數(shù)、返回值、異常、核心邏輯 等的修改。
說(shuō)明: 代碼與注釋更新不同步,就像路網(wǎng)與導(dǎo)航軟件更新不同步一樣,如果導(dǎo)航軟件嚴(yán)重滯后, 就失去了導(dǎo)航的意義。
白話(huà):
很多線(xiàn)上發(fā)生的應(yīng)急事故都是技術(shù)債積累到一定程度,量變導(dǎo)致質(zhì)變,才發(fā)生了重大的災(zāi)難型事件。因此,代碼要與時(shí)俱進(jìn),代碼改變,注釋要改變,文檔要改變,要周知相關(guān)方。問(wèn)題是很難實(shí)現(xiàn),因?yàn)檫@是反人性的,做了這個(gè)事情對(duì)個(gè)人沒(méi)有什么利益,有些人就不做,只有對(duì)代碼有潔癖的人才會(huì)這么做。
8.【參考】注釋掉的代碼盡量要配合說(shuō)明,而不是簡(jiǎn)單的注釋掉。
說(shuō)明: 代碼被注釋掉有兩種可能性:1)后續(xù)會(huì)恢復(fù)此段代碼邏輯。2)永久不用。前者如果沒(méi) 有備注信息,難以知曉注釋動(dòng)機(jī)。后者建議直接刪掉(代碼倉(cāng)庫(kù)保存了歷史代碼)。
白話(huà):
盡量不要留下注釋掉的代碼,曾經(jīng)發(fā)生合并代碼的時(shí)候不小心把注釋的代碼打開(kāi)了,于是,悲劇了,源代碼管理工具,例如:git、svn都有歷史記錄可查詢(xún)的,沒(méi)必要注釋代碼并留在源碼中。
9.【參考】對(duì)于注釋的要求: 第一、能夠準(zhǔn)確反應(yīng)設(shè)計(jì)思想和代碼邏輯;第二、能夠描述業(yè)務(wù)含義,使別的程序員能夠迅速了解到代碼背后的信息。完全沒(méi)有注釋的大段代碼對(duì)于閱讀者形同天書(shū),注釋是給自己看的,即使隔很長(zhǎng)時(shí)間,也能清晰理解當(dāng)時(shí)的思路;注釋也是給繼任者看的,使其能夠快速接替自己的工作。
白話(huà):
還是那句話(huà),這個(gè)是反人性的,注釋好了又有什么用,可以讓更多的人掌握我的代碼嗎?職場(chǎng)上似乎不是一個(gè)好事 :),不過(guò)專(zhuān)業(yè)的程序員更愿意碼好每一段代碼是毋庸置疑的。
10.【參考】好的命名、代碼結(jié)構(gòu)是自解釋的,注釋力求精簡(jiǎn)準(zhǔn)確、表達(dá)到位。避免出現(xiàn)注釋的
一個(gè)極端: 過(guò)多過(guò)濫的注釋?zhuān)a的邏輯一旦修改,修改注釋是相當(dāng)大的負(fù)擔(dān)。
反例:
- // put elephant into fridge
- put(elephant, fridge);
方法名put,加上兩個(gè)有意義的變量名 elephant 和 fridge,已經(jīng)說(shuō)明了這是在干什么,語(yǔ)
義清晰的代碼不需要額外的注釋。
白話(huà):
寫(xiě)注釋的最高境界是不用注釋?zhuān)a一目了然,看了就懂。
11.【參考】特殊注釋標(biāo)記,請(qǐng)注明標(biāo)記人與標(biāo)記時(shí)間。注意及時(shí)處理這些標(biāo)記,通過(guò)標(biāo)記掃描, 經(jīng)常清理此類(lèi)標(biāo)記。線(xiàn)上故障有時(shí)候就是來(lái)源于這些標(biāo)記處的代碼。
1) 待辦事宜(TODO):( 標(biāo)記人,標(biāo)記時(shí)間,[預(yù)計(jì)處理時(shí)間]) 表示需要實(shí)現(xiàn),但目前還未實(shí)現(xiàn)的功能。這實(shí)際上是一個(gè) Javadoc 的標(biāo)簽,目前的 Javadoc
還沒(méi)有實(shí)現(xiàn),但已經(jīng)被廣泛使用。只能應(yīng)用于類(lèi),接口和方法(因?yàn)樗且粋€(gè) Javadoc 標(biāo)簽)。
2) 錯(cuò)誤,不能工作(FIXME):(標(biāo)記人,標(biāo)記時(shí)間,[預(yù)計(jì)處理時(shí)間])
在注釋中用 FIXME 標(biāo)記某代碼是錯(cuò)誤的,而且不能工作,需要及時(shí)糾正的情況。
白話(huà):
我很負(fù)責(zé)人的說(shuō),經(jīng)常使用TODO和FIXME的程序員都是好程序員。
9 其他
1.【強(qiáng)制】在使用正則表達(dá)式時(shí),利用好其預(yù)編譯功能,可以有效加快正則匹配速度。
說(shuō)明: 不要在方法體內(nèi)定義:Pattern pattern = Pattern.compile(規(guī)則);
白話(huà):
Pattern和Random都是線(xiàn)程安全的,而MessageDigest, SimpleDateFormat不是線(xiàn)程安全的。
2.【強(qiáng)制】velocity 調(diào)用 POJO 類(lèi)的屬性時(shí),建議直接使用屬性名取值即可,模板引擎會(huì)自動(dòng)按規(guī)范調(diào)用 POJO 的 getXxx(),如果是 boolean 基本數(shù)據(jù)類(lèi)型變量(boolean 命名不需要加 is 前綴),會(huì)自動(dòng)調(diào)用 isXxx()方法。
說(shuō)明: 注意如果是 Boolean 包裝類(lèi)對(duì)象,優(yōu)先調(diào)用 getXxx()的方法。
白話(huà):
屬性前不要加is,這類(lèi)變量最好用形容詞表達(dá)。
3.【強(qiáng)制】后臺(tái)輸送給頁(yè)面的變量必須加$!{var}——中間的感嘆號(hào)。 說(shuō)明:如果 var=null 或者不存在,那么${var}會(huì)直接顯示在頁(yè)面上。
白話(huà):
這里說(shuō)的是velocity,${var}在var為null的時(shí)候,直接把代碼${var}打印在界面上,太不專(zhuān)業(yè),也有安全問(wèn)題,必須使用${var}代替。
4.【強(qiáng)制】注意 Math.random() 這個(gè)方法返回是 double 類(lèi)型,注意取值的范圍 0≤x<1(能夠取到零值,注意除零異常),如果想獲取整數(shù)類(lèi)型的隨機(jī)數(shù),不要將 x 放大 10 的若干倍然后 取整,直接使用 Random 對(duì)象的 nextInt 或者 nextLong 方法。
白話(huà):
毋庸置疑,用專(zhuān)業(yè)的工具干專(zhuān)業(yè)的事兒。
5.【強(qiáng)制】獲取當(dāng)前毫秒數(shù) System.currentTimeMillis(); 而不是 new Date().getTime();
說(shuō)明: 如果想獲取更加精確的納秒級(jí)時(shí)間值,用 System.nanoTime()。在 JDK8 中,針對(duì)統(tǒng)計(jì)時(shí)間等場(chǎng)景,推薦使用 Instant 類(lèi)。
白話(huà):
Date內(nèi)部也是使用System.currentTimeMillis實(shí)現(xiàn)的,使用Date還得多構(gòu)造一個(gè)對(duì)象,在量級(jí)很大的時(shí)候會(huì)有一些性能損耗。
6.【推薦】盡量不要在 velocity 模板中加入變量聲明、邏輯運(yùn)算符,更不要在模板中加入任何復(fù)雜的邏輯。
白話(huà):
MVC!視圖的職責(zé)是展示,不要搶人家模型和控制器的活!
7.【推薦】任何數(shù)據(jù)結(jié)構(gòu)的構(gòu)造或初始化,都應(yīng)指定大小,避免數(shù)據(jù)結(jié)構(gòu)無(wú)限增長(zhǎng)吃光內(nèi)存。
白話(huà):
尤其是集合、批量參數(shù)、數(shù)據(jù)庫(kù)表都要有最大數(shù)量的限制,否則就為OOM埋下隱患。
8.【推薦】對(duì)于“明確停止使用的代碼和配置”,如方法、變量、類(lèi)、配置文件、動(dòng)態(tài)配置屬性
等要堅(jiān)決從程序中清理出去,避免造成過(guò)多垃圾。
白話(huà):
要保持衛(wèi)生,屋子太亂了也影響生活的情趣,代碼也要定期捯飭一下。
10 總結(jié)
在總結(jié)的過(guò)程中,又對(duì)阿里的每一條規(guī)范進(jìn)行了深入的思考,并與阿里人員以及業(yè)內(nèi)Java專(zhuān)家進(jìn)行了討論,對(duì)難于理解的條目進(jìn)行了說(shuō)明,并進(jìn)行了適當(dāng)?shù)男畔⒀a(bǔ)充,用白話(huà)的形式表達(dá)出來(lái),能夠感覺(jué)到阿里開(kāi)發(fā)規(guī)范的每一條都是線(xiàn)上大規(guī)模服務(wù)化系統(tǒng)中的踩坑指南,這和筆者正在做的事情一樣,線(xiàn)上出了問(wèn)題會(huì)復(fù)盤(pán),復(fù)盤(pán)后找到問(wèn)題的原因,總結(jié)歸納,并在研發(fā)流程規(guī)范中體現(xiàn)解決方案和避免措施,然后推廣給開(kāi)發(fā)人員,讓大家在今后的開(kāi)發(fā)中輕而易舉的避免犯同樣的錯(cuò)誤,從而避免發(fā)生同樣的生產(chǎn)事故,關(guān)于生產(chǎn)事故,向大家推薦電影《深海浩劫》。
本文為五篇系列文章的第一篇-編程規(guī)約,后續(xù)會(huì)陸續(xù)發(fā)布其余的文章,內(nèi)容包括:異常日志、MySQL規(guī)約、工程規(guī)約、安全規(guī)約等。
【本文為51CTO專(zhuān)欄作者“李艷鵬”的原創(chuàng)稿件,轉(zhuǎn)載可通過(guò)作者簡(jiǎn)書(shū)號(hào)(李艷鵬)或51CTO專(zhuān)欄獲取聯(lián)系】

























