無(wú)規(guī)矩不成方圓,編碼規(guī)范就如同協(xié)議侧馅,有了Http危尿、TCP等各種協(xié)議,計(jì)算機(jī)之間才能有效地通信馁痴,同樣的谊娇,有了一致的編碼規(guī)范,程序員之間才能有效地合作罗晕。道理大家都懂济欢,可現(xiàn)實(shí)中的我們,經(jīng)常一邊吐槽別人的代碼小渊,一邊寫著被吐槽的代碼法褥,究其根本,就是缺乏遵從編碼規(guī)范的意識(shí)酬屉!多年前半等,Google發(fā)布Google Java Style
來(lái)定義Java編碼時(shí)應(yīng)遵循的規(guī)范;今年年初阿里則發(fā)布阿里巴巴Java 開(kāi)發(fā)手冊(cè)
呐萨,并隨后迭代了多個(gè)版本杀饵,直至9月份又發(fā)布了pdf終極版。這兩大互聯(lián)網(wǎng)巨頭的初衷谬擦,都是希望能夠統(tǒng)一標(biāo)準(zhǔn)切距,使業(yè)界編碼達(dá)到一致性,提升溝通和研發(fā)效率惨远,這對(duì)于我們碼農(nóng)無(wú)疑是很贊的一筆福利呀谜悟。筆者將兩份規(guī)范都通讀了一遍话肖,其中列舉的不少細(xì)則跟平時(shí)的編碼習(xí)慣基本是符合的,不過(guò)還是有不少新奇的收獲葡幸,忍不住記錄在此最筒,供日后念念不忘~
Java開(kāi)發(fā)規(guī)范總覽
一、Google Java Style
Google的java開(kāi)發(fā)規(guī)范主要分為6大部分:源文件基本規(guī)范礼患、源文件結(jié)構(gòu)是钥、代碼格式、命名缅叠、編程實(shí)踐和Javadoc,各部分概要如下:
1虏冻、源文件基本規(guī)范(source file basics):文件名肤粱、文件編碼、特殊字符的規(guī)范要求
2厨相、源文件結(jié)構(gòu)(source file structure):版權(quán)許可信息领曼、package、import蛮穿、類申明的規(guī)約
3庶骄、代碼格式(formatting):大括號(hào)、縮進(jìn)践磅、換行单刁、列長(zhǎng)限制、空格府适、括號(hào)羔飞、枚舉、數(shù)組檐春、switch語(yǔ)句逻淌、注4、解疟暖、注釋卡儒、和修飾符等格式要求
5、命名(Naming):標(biāo)識(shí)符俐巴、包名骨望、類名、方法名窜骄、常量名锦募、非常量成員名、參數(shù)名邻遏、局部變量的命名規(guī)范
6糠亩、編程實(shí)踐(Programming Practices):@override虐骑、異常捕獲、靜態(tài)成員赎线、Finalizers等用法規(guī)約
二廷没、阿里巴巴Java開(kāi)發(fā)手冊(cè)
阿里的Java開(kāi)發(fā)手冊(cè)相對(duì)于前者更上一層樓,它除了基本的編程風(fēng)格的規(guī)約外垂寥,還給出了日志颠黎、單元測(cè)試、安全滞项、MySQL狭归、工程結(jié)構(gòu)等代碼之外的規(guī)約,據(jù)說(shuō)是阿里近萬(wàn)名開(kāi)發(fā)同學(xué)集體智慧的結(jié)晶文判,相當(dāng)了得过椎,還是挺值得借鑒一下的。各部分概要如下:
1戏仓、編程規(guī)約:命名風(fēng)格疚宇、常量、代碼格式赏殃、OOP敷待、集合處理、并發(fā)仁热、控制語(yǔ)句榜揖、注釋等
2、異常日志:異常處理股耽、日志的命名根盒、保留時(shí)間、輸出級(jí)別物蝙、記錄信息等
3休吠、單元測(cè)試:AIR原則(Automatic,Independent,Repeatable)贷祈、單側(cè)的代碼目錄、目標(biāo),單側(cè)的寫法苞氮,即BCDE原則(Border,Correct,Design,Error)
4芜抒、安全規(guī)約:權(quán)限校驗(yàn)价卤、數(shù)據(jù)脫敏刹帕、參數(shù)有效校驗(yàn)、CSRF安全過(guò)濾票堵、防重放限制扼睬、風(fēng)控策略等
5、MySQL數(shù)據(jù)庫(kù):建表悴势、索引窗宇、SQL語(yǔ)句措伐、ORM映射等
6、工程結(jié)構(gòu):應(yīng)用分層军俊、二方庫(kù)依賴(坐標(biāo)命名侥加、接口約定、pom配置)粪躬、服務(wù)器端各項(xiàng)配置(TCP超時(shí)担败、句柄數(shù)、JVM參數(shù)等)
熟知的規(guī)范
對(duì)于大家已經(jīng)爛熟于心并已習(xí)慣遵守的一些編碼規(guī)范镰官,比如類名提前、常量的命名、數(shù)組的定義朋魔、Long類型的字面等岖研,就不在此一一列出了,只想就一些平時(shí)編碼中較容易個(gè)性化警检,并可能會(huì)存在爭(zhēng)議的規(guī)范進(jìn)行一番探討。為了便于說(shuō)明害淤,用G表示規(guī)范出自于Google Java Style
扇雕,A表示規(guī)范出自于阿里巴巴Java開(kāi)發(fā)手冊(cè)
。
[A]IDE的
text file encoding
設(shè)置為UTF-8窥摄;IDE中文件的換行符使用Unix格式镶奉,不要使用Windows格式([G]文件編碼:UTF-8)
看似簡(jiǎn)單的一個(gè)編碼約定,在實(shí)際開(kāi)發(fā)過(guò)程中卻經(jīng)常出現(xiàn)不一致崭放,由于我們是中文操作系統(tǒng)哨苛,系統(tǒng)編碼是GBK。當(dāng)兩個(gè)協(xié)作的開(kāi)發(fā)人員IDE币砂,一個(gè)采用系統(tǒng)默認(rèn)編碼建峭,一個(gè)設(shè)置為UTF-8,那么二人看對(duì)方寫的中文注釋就各自都是亂碼了决摧,很尷尬亿蒸。對(duì)于“換行符使用Unix格式”,這個(gè)在編寫shell和hive腳本時(shí)踩過(guò)好幾次坑掌桩,而且錯(cuò)誤提示很隱晦边锁,一時(shí)半會(huì)還真察覺(jué)不出來(lái),只能說(shuō)這個(gè)規(guī)范請(qǐng)務(wù)必遵守波岛!
[A]代碼中的命名嚴(yán)禁使用拼音與英文混合的方式茅坛,更不允許直接使用中文的方式。
大多數(shù)程序員還是都會(huì)遵從英文的命名方式则拷,但在實(shí)際工作中還真有遇到過(guò)拼音與英文混用的命名贡蓖,比如創(chuàng)建報(bào)文的函數(shù)命名為createBaowen
曹鸠,看起來(lái)怪怪的,有點(diǎn)不倫不類摩梧。
[A]抽象類命名使用Abstract或Base開(kāi)頭物延;異常類使用Exception結(jié)尾;測(cè)試類以它要測(cè)試的類的名稱開(kāi)始仅父,以Test結(jié)尾
以spring源碼為例叛薯,其抽象類都是以Abstract開(kāi)頭,異常類以Exception結(jié)尾笙纤,測(cè)試類則是以Tests結(jié)尾耗溜。
[A]POJO類中布爾類型的變量,都不要加is省容,否則部分框架解析會(huì)引起序列化錯(cuò)誤抖拴。
這個(gè)問(wèn)題一說(shuō)大家都知道,但實(shí)際卻是很容易被忽視腥椒!因?yàn)锽oolean通常表達(dá)“是”或“否”的意思阿宅,可能一遇到布爾變量,大家會(huì)習(xí)慣性地將它與is關(guān)聯(lián)起來(lái)笼蛛,“很自然”地就會(huì)以is開(kāi)頭定義變量洒放。但筆者想說(shuō)的是,這其實(shí)反應(yīng)了至少兩個(gè)問(wèn)題:1滨砍、對(duì)JavaBean屬性命名規(guī)范不熟往湿;2、對(duì)框架解析POJO的原理不熟惋戏,如RPC反向解析领追、spring MVC參數(shù)綁定、MyBatis處理映射等响逢。
private boolean isActive;
//lombok绒窑、Eclipse生成getter、setter的結(jié)果如下龄句,框架會(huì)誤把變量解析成active
public boolean isActive() {
return isActive;
}
public void setActive(boolean isActive) {
this.isActive = isActive;
}
在搞清這兩個(gè)問(wèn)題前回论,還是建議老老實(shí)實(shí)按規(guī)范來(lái)吧。
包名統(tǒng)一使用小寫分歇,點(diǎn)分隔符之間有且僅有一個(gè)自然語(yǔ)義的英語(yǔ)單詞傀蓉。包名統(tǒng)一使用單數(shù)形式,類名若有復(fù)數(shù)含義职抡,則可使用復(fù)數(shù)形式葬燎。
實(shí)際工作中看到過(guò)包名包含下劃線的,如org.sherlockyb.user_manage.dao
,還是有必要統(tǒng)一一下谱净。
[A]不允許任何魔法值(即未經(jīng)定義的常量)直接出現(xiàn)在代碼中窑邦。
反例:String key = "Id#taobao_" + tradeId;
cache.put(key, value);
避免硬編碼問(wèn)題是每個(gè)程序員都應(yīng)該具備的基本素養(yǎng),硬編碼所帶來(lái)的可讀性差壕探、維護(hù)困難等問(wèn)題冈钦,眾所周知。
[A,G]采用空格縮進(jìn)李请,禁止使用tab字符瞧筛。
這是Google和ali一致的規(guī)約,只不過(guò)前者是一個(gè)tab對(duì)應(yīng)2個(gè)空格导盅,后者則是4個(gè)空格较幌。之所以不提倡tab鍵,是因?yàn)椴煌腎DE對(duì)tab鍵的“翻譯”默認(rèn)有所差異白翻,容易因不同程序員的個(gè)性化而導(dǎo)致同一份代碼的格式混亂乍炉。
[A,G]單行字符數(shù)限制不超過(guò)120/100個(gè)字符,超出需要換行滤馍,換行時(shí)遵循如下規(guī)則:
1)[A,G]第二行相對(duì)于第一行縮進(jìn)4個(gè)空格岛琼,從第三行開(kāi)始,不再繼續(xù)縮進(jìn)巢株。
2)[A]運(yùn)算符或方法調(diào)用的點(diǎn)符號(hào)與下文一起換行([G]若是非賦值運(yùn)算符衷恭,則在該符號(hào)前斷開(kāi);若是賦值運(yùn)算符或foreach
中的分號(hào)纯续,則在該符號(hào)后斷開(kāi))。
4)[A]方法調(diào)用時(shí)灭袁,多個(gè)參數(shù)猬错,需要換行時(shí),在逗號(hào)后進(jìn)行([G]逗號(hào)與前面的內(nèi)容留在同一行)茸歧。
5)在括號(hào)前不要換行倦炒。
對(duì)于單行字符限制,阿里的是120软瞎,Google的是100逢唤。個(gè)人覺(jué)得120略長(zhǎng),特別是當(dāng)用筆記本碼代碼時(shí)涤浇,對(duì)于超限的代碼行鳖藕,經(jīng)常要用橫向滾動(dòng)條,不太友好只锭,個(gè)人推薦100的限制著恩。
沒(méi)有必要增加若干空格來(lái)使某一行的字符與上一行對(duì)應(yīng)位置的字符對(duì)齊。
在變量較多時(shí),這種對(duì)齊是一種累贅喉誊。雖說(shuō)有IDE的自動(dòng)格式化功能邀摆,但多人協(xié)作時(shí),難保各自的格式化沒(méi)有差異伍茄,會(huì)因格式變化而造成不必要的代碼行改動(dòng)栋盹,無(wú)疑會(huì)給你的代碼合并徒增困擾。
方法體內(nèi)的執(zhí)行語(yǔ)句組敷矫、變量的定義語(yǔ)句組例获、不同的業(yè)務(wù)邏輯之間或者不同的語(yǔ)義之間插入一個(gè)空行。相同業(yè)務(wù)邏輯和語(yǔ)義之間不需要插入空行沪饺。
代碼分塊就如同文章分段躏敢,整潔的代碼具有更強(qiáng)的自解釋性。
外部正在調(diào)用或者二方庫(kù)依賴的接口整葡,不允許修改方法簽名件余,避免對(duì)接口調(diào)用方產(chǎn)生影響。作為提供方,接口過(guò)時(shí)必須加@Deprecated注解峻黍,并清晰地說(shuō)明采用的新接口或者新服務(wù)是什么披诗;作為調(diào)用方,有義務(wù)去考證過(guò)時(shí)方法的新實(shí)現(xiàn)是什么端壳。
接口契約,是使用方和調(diào)用方良好協(xié)作的有效保障枪蘑,請(qǐng)務(wù)必遵守损谦。
所有的相同類型的包裝類對(duì)象之間值的比較,全部用equals方法比較岳颇。
說(shuō)明:對(duì)于Integer var = ?
在-128至127范圍內(nèi)的賦值照捡,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)行判斷新博。
這里補(bǔ)充幾點(diǎn)薪夕,除了Integer,其他包裝類型如Long叭披、Byte等都有各自的cache寥殖。這里只提到了等值比較玩讳,對(duì)于>,<等非等值比較,沒(méi)必要手動(dòng)拆箱去比較嚼贡,包裝類型之間直接可以比較大小熏纯,親測(cè)有效。例如:
Long a = new Long(1000L);
Long b = new Long(222L);
Long c = new Long(2000L);
Assert.isTrue(a > b && a < c); //斷言成功
[A]關(guān)于基本數(shù)據(jù)類型與包裝數(shù)據(jù)類型的使用標(biāo)準(zhǔn)如下:
1)所有的POJO類屬性必須使用包裝數(shù)據(jù)類型粤策。
2)RPC方法的返回值和參數(shù)必須使用包裝數(shù)據(jù)類型樟澜。
3)所有的局部變量使用基本數(shù)據(jù)類型。
說(shuō)明:POJO類屬性沒(méi)有初值是提醒使用者在需要使用時(shí)叮盘,必須自己顯式地進(jìn)行賦值秩贰,任何NPE問(wèn)題,或者入口檢查柔吼,都由使用者來(lái)保證毒费。
基本類型作為入?yún)⒑头祷刂涤卸喾N弊病,如不情愿的默認(rèn)值愈魏,NPE風(fēng)險(xiǎn)等觅玻,除了局部變量,其他慎用培漏。
序列化類新增屬性時(shí)溪厘,請(qǐng)不要修改serialVersionUID字段,避免反序列化失斉票畸悬;如果完全不兼容升級(jí),避免反序列化混亂珊佣,那么請(qǐng)修改serialVersionUID值蹋宦。
serialVersionUID是Java為每個(gè)序列化類產(chǎn)生的版本標(biāo)識(shí):版本相同,相互之間則可序列化和反序列化咒锻;版本不同妆档,反序列化時(shí)會(huì)拋出InvalidClassException。因不同的jdk編譯很可能會(huì)生成不同的serialVersionUID默認(rèn)值虫碉,通常需要顯式指定,如1L胸梆。
[A]final可以聲明類敦捧、成員變量、方法碰镜、以及本地變量兢卵,下列情況使用final關(guān)鍵字:
1)不允許被繼承的類,如:String類绪颖。
2)不允許修改引用的域?qū)ο蠡嗷纾纾篜OJO類的域變量甜奄。
3)不允許被重寫的方法,如:POJO類的setter方法窃款。
4)不允許運(yùn)行過(guò)程中重新賦值的局部變量课兄,如傳遞給匿名內(nèi)部類的局部變量。
final關(guān)鍵字有諸多好處晨继,比如JVM和Java應(yīng)用都會(huì)緩存final變量烟阐,以提高性能;final變量可在多線程環(huán)境下放心共享紊扬,無(wú)需額外的同步開(kāi)銷蜒茄;JVM會(huì)對(duì)final修飾的方法、變量及類進(jìn)行優(yōu)化等餐屎,詳情可見(jiàn)深入理解Java中的final關(guān)鍵字檀葛。
慎用Object的clone方法來(lái)拷貝對(duì)象。
說(shuō)明:對(duì)象的clone方法默認(rèn)是淺拷貝腹缩,特別是引用類型成員屿聋。若想實(shí)現(xiàn)深拷貝,需要重寫clone方法實(shí)現(xiàn)屬性對(duì)象的拷貝庆聘。
Java中的賦值操作都是值傳遞胜臊,比如我們常用來(lái)“復(fù)制”DTO的工具,無(wú)論是spring的BeanUtils.copyProperties伙判,還是Apache commons的BeanUtils.cloneBean象对,實(shí)際上也只是兩個(gè)DTO之間成員的引用復(fù)制,成員指向的對(duì)象還是同一個(gè)宴抚,用到此類工具的時(shí)候要有這個(gè)意識(shí)勒魔,不然容易踩坑。
[A]類成員與方法訪問(wèn)控制從嚴(yán):
1)如果不允許外部直接通過(guò)new來(lái)創(chuàng)建對(duì)象菇曲,那么構(gòu)造方法必須是private冠绢。
2)工具類不允許有public或default構(gòu)造方法。
3)類非static成員變量并且與子類共享常潮,必須是protected弟胀。
4)類非static成員變量并且僅在本類使用,必須是private喊式。
5)類static成員變量如果僅在本類使用孵户,必須是private。
6)若是static成員變量岔留,必須考慮是否為final夏哭。
7)類成員方法只供類內(nèi)部調(diào)用,必須是private献联。
8)類成員方法只對(duì)繼承類公開(kāi)竖配,那么限制為protected何址。
說(shuō)明:任何類、方法进胯、參數(shù)用爪、變量,嚴(yán)控訪問(wèn)范圍龄减。過(guò)于寬泛的訪問(wèn)范圍项钮,不利于模塊解耦。
最小權(quán)限原則(Principal of least privilege希停,POLP)是每個(gè)程序員應(yīng)遵守的烁巫,可有效避免數(shù)據(jù)以及功能受到錯(cuò)誤或惡意行為的破壞。
[A]ArrayList的subList結(jié)果不可強(qiáng)轉(zhuǎn)成ArrayList宠能,否則會(huì)拋出ClassCastException異常亚隙。
這里補(bǔ)充一點(diǎn),SubList并未實(shí)現(xiàn)Serializable接口违崇,若RPC接口的List類型參數(shù)接受了SubList類型的實(shí)參阿弃,則在RPC調(diào)用時(shí)會(huì)報(bào)出序列化異常。比如我們常用的guava中的Lists.partition羞延,切分后的子list實(shí)際都是SubList類型渣淳,在傳給RPC接口之前,需要用new ArrayList()包一層伴箩,否則會(huì)報(bào)序列化異常入愧。
[A]在subList場(chǎng)景中,高度注意對(duì)原集合元素個(gè)數(shù)的修改嗤谚,會(huì)導(dǎo)致子列表的遍歷棺蛛、增加、刪除均會(huì)產(chǎn)生ConcurrentModificationException異常巩步。
這個(gè)還是得從源碼的角度來(lái)解釋旁赊。SubList在構(gòu)造時(shí)實(shí)際是直接持有了原list的引用,其add椅野、remove等操作實(shí)際都是對(duì)原list的操作终畅,我們不妨以add為例:
public void add(int index, E element) {
rangeCheckForAdd(index);
checkForComodification(); // 檢查this.modCount與原list的modCount是否一致
l.add(index+offset, element); // 原list新增了一個(gè)元素
this.modCount = l.modCount; // 將原list更新后的modCount同步到this.modCount
size++;
}
可以看出,SubList生成之后竟闪,通過(guò)SubList進(jìn)行add声离、remove等操作時(shí),modCount會(huì)同步更新瘫怜,所以沒(méi)問(wèn)題;而如果此后還對(duì)原list進(jìn)行add本刽、remove等操作鲸湃,SubList是感知不到modCount的變化的赠涮,會(huì)造成modCount不一致,從而報(bào)出ConcurrentModificationException異常暗挑。故通常來(lái)講笋除,從原list取了SubList之后,是不建議再對(duì)原list做結(jié)構(gòu)上的修改的炸裆。
[A]使用工具類Arrays.asList()把數(shù)組轉(zhuǎn)換成集合時(shí)垃它,不能使用其修改集合相關(guān)的方法鸿摇,它的add/remove/clear方法會(huì)拋出UnsupportedOperationException異常渐逃。
類似的批幌,guava的Maps.toMap方法番官,返回的是一個(gè)ImmutableMap
椎例,是不可變的锌半,不能對(duì)其調(diào)用add昌粤、remove等操作窍蓝,使用時(shí)應(yīng)該有這個(gè)意識(shí)土思!
在JDK7版本及版本以上务热,Comparator必須滿足:1)x,y比較結(jié)果和y,x比較結(jié)果相反;2)x>y,y>z,則x>z己儒;3)x=y,則x,z比較結(jié)果和y,z比較結(jié)果相同崎岂。不然Arrays.sort,Collections.sort會(huì)報(bào)IllegalArgumentException異常闪湾。
JDK從1.6升到1.7之后冲甘,默認(rèn)排序算法由MergeSort變?yōu)?a target="_blank">TimSort,對(duì)于任意兩個(gè)比較元素x响谓、y损合,其Comparator結(jié)果一定要是確定的,特別是對(duì)于x=y的情況娘纷,確定返回0嫁审,否則可能出現(xiàn)Comparison method violates its general contract!
錯(cuò)誤。
[A]線程池不允許使用Executors去創(chuàng)建赖晶,而是通過(guò)ThreadPoolExecutor的方式律适,這樣的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)遏插。
說(shuō)明:Executors返回的線程池對(duì)象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允許的請(qǐng)求隊(duì)列長(zhǎng)度為Integer.MAX_VALUE捂贿,可能會(huì)堆積大量的請(qǐng)求,從而導(dǎo)致OOM胳嘲。
2)CachedThreadPool和ScheduledThreadLocal:允許的創(chuàng)建線程數(shù)為Integer.MAX_VALUE厂僧,可能會(huì)創(chuàng)建大量的線程,從而導(dǎo)致OOM了牛。
現(xiàn)在一般很少會(huì)用Executors去創(chuàng)建線程池了颜屠,通常會(huì)使用spring的ThreadPoolExecutorFactoryBean
或者guava的MoreExecutors.listeningDecorator
對(duì)前者包裝一下辰妙,對(duì)于像線程數(shù)、隊(duì)列大小等都是通過(guò)配置來(lái)設(shè)定甫窟。
[A]高并發(fā)時(shí)密浑,同步調(diào)用應(yīng)該去考量鎖的性能損耗。能用無(wú)鎖數(shù)據(jù)結(jié)構(gòu)粗井,就不要用鎖尔破;能鎖區(qū)塊,就不要鎖整個(gè)方法體浇衬;能用對(duì)象鎖懒构,就不要用類鎖。
一句話概括就是径玖,能不鎖就不鎖痴脾,即便鎖,也盡量使鎖的粒度最小化梳星。
[A]表達(dá)異常分支時(shí)赞赖,少用if-else方式,可使用衛(wèi)語(yǔ)句代替冤灾。對(duì)于
if()...else if()...else...
方式前域,請(qǐng)勿超過(guò)3層。對(duì)于超過(guò)的韵吨,可使用衛(wèi)語(yǔ)句匿垄、策略模式、狀態(tài)模式等來(lái)實(shí)現(xiàn)归粉。
if(condition) {
...
return obj;
}
// 接著寫else的業(yè)務(wù)邏輯代碼;
冗長(zhǎng)的if-else可讀性差椿疗,維護(hù)困難,推薦使用衛(wèi)語(yǔ)句糠悼,邏輯清晰明了届榄。
[A]代碼修改的同時(shí),注釋也做同步修改倔喂,尤其是參數(shù)铝条、返回值、異常席噩、核心邏輯等的修改班缰。
這個(gè)在實(shí)際工程代碼中還真看到過(guò)不少,代碼與注釋牛頭不對(duì)馬嘴悼枢,盡量別留坑給后來(lái)者埠忘,應(yīng)該算在程序猿的基本素養(yǎng)之內(nèi)吧。
謹(jǐn)慎注釋掉代碼。在上方詳細(xì)說(shuō)明莹妒,而不是簡(jiǎn)單的注釋掉假丧。如果無(wú)用,則刪除动羽。
說(shuō)明:代碼被注釋掉有兩種可能:1)后續(xù)會(huì)恢復(fù)此段代碼邏輯。2)永久不用渔期。前者如果沒(méi)有備注信息运吓,難以知曉注釋動(dòng)機(jī)。后者建議直接刪掉(代碼倉(cāng)庫(kù)保存了歷史代碼)疯趟。
這個(gè)就更無(wú)力吐槽了拘哨,比上一條更常見(jiàn),so信峻,這條規(guī)范強(qiáng)烈推薦倦青!
1)對(duì)于注釋的要求:第一、能準(zhǔn)確反映設(shè)計(jì)思想和代碼邏輯盹舞;第二产镐、能描述業(yè)務(wù)含義,使別人能迅速了解到代碼背后的信息踢步;第三癣亚、好的命名、代碼結(jié)構(gòu)是自解釋性的获印,注釋力求精簡(jiǎn)準(zhǔn)確述雾、表達(dá)到位。避免過(guò)多過(guò)濫的注釋兼丰。
2)finally塊必須對(duì)資源對(duì)象玻孟、流對(duì)象進(jìn)行關(guān)閉,有異常也要做try-catch鳍征。若是JDK7及以上黍翎,可使用try-with-resources
。不能再finally塊中使用return蟆技,finally塊中的return返回后方法結(jié)束執(zhí)行玩敏,不會(huì)再執(zhí)行try塊中的return語(yǔ)句。
3)防止NPE质礼,是程序員的基本素養(yǎng)旺聚,注意NPE產(chǎn)生的場(chǎng)景:
1.返回類型為基本數(shù)據(jù)類型,return包裝數(shù)據(jù)類型的對(duì)象時(shí)眶蕉,自動(dòng)拆箱有可能產(chǎn)生NPE
2.數(shù)據(jù)庫(kù)的查詢結(jié)果可能為null砰粹。
3.遠(yuǎn)程調(diào)用返回對(duì)象時(shí),一律要求進(jìn)行空指針判斷,防止NPE碱璃。
4.對(duì)于Session中獲取的數(shù)據(jù)弄痹,建議NPE檢查,避免空指針嵌器。
5.級(jí)聯(lián)調(diào)用obj.getA().getB().getC()肛真;一連串調(diào)用,易產(chǎn)生NPE爽航。正例:使用JDK8的Optional類來(lái)防止NPE問(wèn)題蚓让。
4)在代碼中使用“拋異常”還是“返回錯(cuò)誤碼”讥珍,對(duì)于公司外的http/api開(kāi)放接口必須使用“錯(cuò)誤碼”历极;而應(yīng)用內(nèi)部推薦異常拋出;跨應(yīng)用間RPC調(diào)用優(yōu)先考慮使用Result方式衷佃,封裝isSuccess()方法趟卸、“錯(cuò)誤碼”、“錯(cuò)誤簡(jiǎn)短信息”氏义。
5)避免出現(xiàn)重復(fù)的代碼(Don't Repeat Yourself)锄列,即DRY原則。
以上幾條觅赊,皆是毫無(wú)爭(zhēng)議的基本規(guī)范右蕊,且行且遵守。
1)日志文件推薦至少保存15天吮螺,因?yàn)橛行┊惓>邆湟浴爸堋睘轭l次發(fā)生的特點(diǎn)饶囚。
2)對(duì)trace/debug/info級(jí)別的日志輸出,必須使用條件輸出形式或者使用占位符的方式鸠补。以避免不必要的字符串拼接萝风,浪費(fèi)系統(tǒng)資源。
3)避免重復(fù)打印日志紫岩,浪費(fèi)磁盤空間规惰,對(duì)于特定包的日志,務(wù)必設(shè)置additivity=false
泉蝌。
4)異常信息應(yīng)該包括兩類信息:案發(fā)現(xiàn)場(chǎng)信息和異常堆棧信息歇万。如果不處理,則通過(guò)關(guān)鍵字throws往上拋勋陪。
關(guān)于日志的幾條不錯(cuò)的規(guī)范贪磺。日志作為服務(wù)器行為的日常軌跡,對(duì)于統(tǒng)計(jì)分析诅愚、故障排錯(cuò)意義巨大寒锚,要慎重對(duì)待才是。
1)好的單元測(cè)試必須遵守AIR原則。
A:Automatic(自動(dòng)化)刹前。全自動(dòng)執(zhí)行泳赋,非交互式的。使用assert驗(yàn)證喇喉,而非System.out硅堆。
I:Independent(獨(dú)立性)挨务。單側(cè)用例之間不能產(chǎn)生依賴晾虑,互相獨(dú)立毅厚。
R:Repeatable(可重復(fù))」В可重復(fù)執(zhí)行,不能受到外界環(huán)境的影響制妄。對(duì)于外部依賴掸绞,通過(guò)spring等DI框架注入一個(gè)本地(內(nèi)存)實(shí)現(xiàn)或者M(jìn)ock實(shí)現(xiàn)。
2)單元測(cè)試的基本目標(biāo):語(yǔ)句覆蓋率達(dá)到70%耕捞;核心模塊的語(yǔ)句覆蓋率和分支覆蓋率都要達(dá)到100%衔掸。
3)編寫單元測(cè)試代碼遵守BCDE原則:
B:Border,邊界值測(cè)試俺抽,包括循環(huán)邊界敞映、特殊取值、特殊時(shí)間點(diǎn)磷斧、數(shù)據(jù)順序等振愿。
C:Correct,正確的輸入弛饭,并得到預(yù)期的結(jié)果冕末。
D:Design,與設(shè)計(jì)文檔相結(jié)合侣颂,來(lái)編寫單元測(cè)試档桃。
E:Error,強(qiáng)制錯(cuò)誤信息輸入(如:非法數(shù)據(jù)憔晒、異常流程藻肄、非業(yè)務(wù)允許輸入等),并得到預(yù)期結(jié)果拒担。
關(guān)于單元測(cè)試的幾條不錯(cuò)的規(guī)范嘹屯。單元測(cè)試是代碼質(zhì)量的有效保障!太多的想當(dāng)然澎蛛、自以為是抚垄,往往會(huì)跳過(guò)單測(cè),最終自食其果。曾經(jīng)的筆者也犯過(guò)類似毛病呆馁,還好及時(shí)糾正桐经。
新奇的收獲
這里將列出一些筆者覺(jué)得有新收獲的規(guī)范,有的是平時(shí)編碼過(guò)程中沒(méi)有嚴(yán)格遵守的浙滤,比如switch中default偶爾加偶爾不加阴挣;有的則是目前還不太清楚的規(guī)范。
[A]杜絕完全不規(guī)范的縮寫纺腊,避免望文不知義畔咧。
反例:AbstractClass的“縮寫”命名成AbsClass;condition的“縮寫”命名成condi揖膜,此類隨意縮寫嚴(yán)重降低了代碼的可閱讀性誓沸。
說(shuō)來(lái)慚愧,這類不規(guī)范的縮寫壹粟,筆者之前還真干過(guò)幾次拜隧。有時(shí)候是覺(jué)著變量太長(zhǎng),導(dǎo)致明明邏輯很簡(jiǎn)單的一條語(yǔ)句趁仙,就超過(guò)了列限制洪添,于是乎主觀地縮寫命名,如mergedRegionReportDtos縮寫為mRegReportDtos雀费,accountIdToHourReportDtos縮寫為accountIdToHrDtos干奢,相當(dāng)混亂有木有!所以盏袄,如果對(duì)英文單詞的縮寫拿不定的話忿峻,還是直接用原單詞吧,長(zhǎng)點(diǎn)就長(zhǎng)點(diǎn)辕羽,可讀性很重要炭菌。
[A]如果模塊、接口逛漫、類黑低、方法使用了設(shè)計(jì)模式,在命名時(shí)體現(xiàn)出具體模式酌毡,有利于閱讀者快速理解架構(gòu)設(shè)計(jì)理念克握。類示例:OrderFactory、LoginProxy枷踏、ResourceObserver菩暗。
沒(méi)啥好說(shuō)的,同樣是為了提升代碼的自解釋性旭蠕。spring源碼中隨處可見(jiàn)這樣的命名風(fēng)格:AbstractAutowireCapableBeanFactory
停团、Cglib2AopProxy
旷坦、BeanDefinitionParserDelegate
等
[A]接口類中的方法和屬性不要加任何修飾符號(hào)(public也不要加),保持代碼的簡(jiǎn)潔性佑稠,并加上有效的Javadoc注釋秒梅。盡量不要在接口里定義變量,如果一定要定義變量舌胶,肯定是與接口方法有關(guān)捆蜀,并且是整個(gè)應(yīng)用的基礎(chǔ)常量。
正例:接口方法簽名:void f();
接口基礎(chǔ)常量表示:String COMPANY = "alibaba";
反例:接口方法定義:public abstract void f();
說(shuō)明:JDK8中接口允許有默認(rèn)實(shí)現(xiàn)幔嫂,那么這個(gè)default方法辆它,是對(duì)所有實(shí)現(xiàn)類都有價(jià)值的默認(rèn)實(shí)現(xiàn)。
目前所見(jiàn)過(guò)的組內(nèi)代碼履恩,有太多的接口中方法都是加了public锰茉,也許是后來(lái)的編碼者看到前任留下的已有方法都加了,為了保持一致切心,于是乎也加了public洞辣。說(shuō)到底還是最初的良好規(guī)范沒(méi)有形成,導(dǎo)致給后來(lái)者以錯(cuò)誤的指引昙衅!簡(jiǎn)單才是美,把public 去掉吧定鸟。
[A]接口的命名規(guī)則:如果是形容能力的接口名稱而涉,取對(duì)應(yīng)的形容詞做接口名(通常是-able的形式)
正例:AbstractTranslator實(shí)現(xiàn)Translatable
Log4j中的AppenderAttachable,JDK中的AutoCloseable联予,Appendable等啼县。
[A]各層命名規(guī)約:
A)Service/DAO層方法命名前綴規(guī)約
1)獲取對(duì)象時(shí),單個(gè)用get/多個(gè)用list沸久;2)獲取統(tǒng)計(jì)值用count
3)插入用save/insert季眷;4)刪除用remove/delete;5)修改用update
關(guān)于資源的CRUD卷胯,這塊的方法命名相當(dāng)亂子刮,太容易個(gè)性化了!至少目前組內(nèi)代碼窑睁,要啥有啥:query與get并存挺峡,查詢列表和計(jì)數(shù)的都是get,并未做區(qū)分担钮;一會(huì)兒remove橱赠,一會(huì)兒delete;既有save也有insert箫津。當(dāng)你Ctrl+O的時(shí)候狭姨,想找個(gè)count某元素的方法時(shí)賊費(fèi)勁宰啦,急需統(tǒng)一!
[A]不要使用一個(gè)常量類維護(hù)所有常量饼拍,按常量功能進(jìn)行歸類赡模,分開(kāi)維護(hù)。
說(shuō)明:大而全的常量類惕耕,非得使用查找功能才能定位到修改的常量纺裁,不利于理解和維護(hù)。
正例:緩存相關(guān)常量放在類CacheConsts下司澎,系統(tǒng)配置相關(guān)常量放在類ConfigConsts下欺缘。
[A]常量的復(fù)用層次有五層:跨應(yīng)用共享常量、應(yīng)用內(nèi)共享常量挤安、子工程內(nèi)共享常量谚殊、包內(nèi)共享常量、類內(nèi)共享常量蛤铜。
1)跨應(yīng)用共享常量:放置在二方庫(kù)中嫩絮,通常是client.jar中的constant目錄下。
2)應(yīng)用內(nèi)共享常量:放置在一方庫(kù)中围肥,通常是modules中的constant目錄下剿干。
3)子工程內(nèi)共享常量:當(dāng)前子工程的constant目錄下。
4)包內(nèi)共享常量:當(dāng)前包下單獨(dú)的constant目錄下穆刻。
5)類內(nèi)共享常量:直接在類內(nèi)部private static final定義置尔。
常量的維護(hù)也可運(yùn)用設(shè)計(jì)模式思想,單一職責(zé)氢伟,分層榜轿,嚴(yán)格控制作用域,使常量更清晰朵锣,易于理解谬盐,便于維護(hù)。
[A]類內(nèi)方法定義順序依次是:共有方法或保護(hù)方法 > 私有方法 > getter/setter方法诚些。但有個(gè)規(guī)則特例:[A,G]當(dāng)一個(gè)類有多個(gè)構(gòu)造方法飞傀,或者多個(gè)同名方法,這些方法應(yīng)該按順序放置在一起诬烹。即重載永不分離助析。
說(shuō)明:共有方法是類的調(diào)用者和維護(hù)者最關(guān)系的方法,首屏展示最好椅您;保護(hù)方法雖然只是子類關(guān)心外冀,也可能是“模板設(shè)計(jì)模式”下的核心方法;而私有方法外部一般不需要特別關(guān)心掀泳,是一個(gè)黑盒實(shí)現(xiàn)雪隧;因?yàn)槌休d的信息價(jià)值較低西轩,所有Service和DAO的getter/setter方法放在類的最后。
方法的排版要有秩序脑沿,這樣在我們Ctrl+O
的時(shí)候才能更方便的查閱方法列表藕畔。阿里的約定是比較通用的規(guī)則,對(duì)此庄拇,Google的看法則不同注服,它認(rèn)為類的成員順序不存在唯一的通用法則,重要的是措近,每個(gè)類應(yīng)該以維護(hù)者所能解釋的排序邏輯去排序它的成員溶弟。常見(jiàn)的反例:新的方法總是習(xí)慣性地添加到類的結(jié)尾,排序毫無(wú)意義瞭郑。
[A]對(duì)多個(gè)資源勺美、數(shù)據(jù)庫(kù)表皆警、對(duì)象同時(shí)加鎖時(shí)推溃,需要保持一致的加鎖順序诉植,否則可能會(huì)造成死鎖。
說(shuō)明:線程一需要對(duì)表A阁谆、B碳抄、C依次全部加鎖后才可以進(jìn)行更新操作,那么線程二的加鎖順序也必須是A场绿、B剖效、C,否則可能出現(xiàn)死鎖裳凸。
從死鎖產(chǎn)生的條件出發(fā)來(lái)避免死鎖。比如我們根據(jù)一批ids批量更新數(shù)據(jù)庫(kù)記錄時(shí)劝贸,預(yù)先對(duì)ids排序姨谷,也是一種能有效降低死鎖發(fā)生概率的措施。
[A]使用CountDownLatch進(jìn)行異步轉(zhuǎn)同步操作映九,每個(gè)線程退出前必須調(diào)用countDown方法梦湘,線程執(zhí)行代碼注意catch異常,確保countDown方法被執(zhí)行到件甥,避免主線程無(wú)法執(zhí)行至await方法捌议,直到超時(shí)才返回結(jié)果。
避免Random實(shí)例被多線程使用引有,雖然共享該實(shí)例是線程安全的瓣颅,但會(huì)因競(jìng)爭(zhēng)同一seed導(dǎo)致的性能下降。
說(shuō)明:Random實(shí)例包括java.util.Random的實(shí)例或者M(jìn)ath.random的方式譬正。
正例:在JDK7之后宫补,可以直接使用API ThreadLocalRandom檬姥,而在JDK7之前,需要編碼保證每個(gè)線程持有一個(gè)實(shí)例粉怕。
volatile關(guān)鍵字解決多線程內(nèi)存不可見(jiàn)問(wèn)題健民。對(duì)于一寫多讀,是可以解決變量同步問(wèn)題贫贝,但是如果多寫秉犹,同樣無(wú)法解決線程安全問(wèn)題。如果是count++操作稚晚,使用如下類實(shí)現(xiàn):
AtomicInteger count = new AtomicInteger(); count.addAndGet(1)
崇堵; 如果是JDK8,推薦使用LongAdder對(duì)象蜈彼,比AtomicLong性能更好(減少樂(lè)觀鎖的重試次數(shù))筑辨。
volatile關(guān)鍵字只是保證了同一個(gè)變量在多線程中的可見(jiàn)性,更多的是用于修飾作為開(kāi)關(guān)狀態(tài)的變量幸逆。但是volatile只提供了內(nèi)存可見(jiàn)性棍辕,而沒(méi)有提供原子性!volatile變量在每次被線程訪問(wèn)時(shí)还绘,都強(qiáng)迫從主內(nèi)存中重讀該變量的值楚昭,而當(dāng)該變量發(fā)生變化時(shí),又會(huì)強(qiáng)迫線程將最近的值刷新到主內(nèi)存拍顷,對(duì)于像boolean flag = true
等原子性賦值操作是沒(méi)問(wèn)題的抚太,但volatile不能保證復(fù)合操作的原子性,如count++
昔案。
[A]除常用方法(如getXxx/isXxx)等外尿贫,不要在條件判斷中執(zhí)行其他復(fù)雜的語(yǔ)句,將復(fù)雜邏輯判斷的結(jié)果賦值給一個(gè)有意義的布爾變量名踏揣,以提高可讀性庆亡。
這個(gè)筆者之前確實(shí)有過(guò)這樣的壞習(xí)慣,為了省略一條賦值語(yǔ)句捞稿,將if中的條件搞得比較復(fù)雜又谋,代碼冗長(zhǎng),可讀性也差娱局,得不償失彰亥。
[A]參數(shù)校驗(yàn)與否:
需要校驗(yàn)的:1)對(duì)外提供的開(kāi)發(fā)接口,不管是RPC/API/HTTP接口衰齐;2)敏感權(quán)限入口任斋;3)需要極高穩(wěn)定性和可用性的方法
不需校驗(yàn)的:1)極有可能被循環(huán)調(diào)用的方法。但在方法說(shuō)明里必須注明外部參數(shù)檢查要求耻涛。2)底層調(diào)用頻度較高的方法仁卷。如一般Service會(huì)做參數(shù)校驗(yàn)穴翩,到了DAO層,參數(shù)校驗(yàn)可省略锦积。3)被聲明為private只會(huì)被自己代碼所調(diào)用的方法芒帕,如果能確定傳入?yún)?shù)已做過(guò)檢查或者肯定不會(huì)有問(wèn)題,此時(shí)可不校驗(yàn)參數(shù)丰介。
過(guò)多的參數(shù)校驗(yàn)背蟆,不僅是冗余代碼,而且還影響性能哮幢,只在必要的時(shí)候做校驗(yàn)带膀。
1)隸屬于用戶個(gè)人的頁(yè)面或功能必須進(jìn)行權(quán)限控制校驗(yàn)。說(shuō)明:防止沒(méi)有做水平權(quán)限校驗(yàn)就可隨意訪問(wèn)橙垢、修改垛叨、刪除別人的數(shù)據(jù)。
2)用戶請(qǐng)求傳入的任何參數(shù)必須做有效性校驗(yàn)柜某。忽略參數(shù)校驗(yàn)可能導(dǎo)致:1)page size過(guò)大導(dǎo)致內(nèi)存溢出嗽元;2)惡意order by導(dǎo)致數(shù)據(jù)庫(kù)慢查詢;3)任意重定向喂击;4)SQL注入剂癌;5)反序列化注入;6)正則輸入源串拒絕服務(wù)ReDos
3)表單翰绊、AJAX提交必須執(zhí)行CSRF(Cross-site request forgery)安全過(guò)濾
4)在使用平臺(tái)資源佩谷,譬如短信、郵件监嗜、電話谐檀、下單、支付裁奇,必須實(shí)現(xiàn)正確的防重放機(jī)制桐猬,如數(shù)量限制、疲勞度控制框喳、驗(yàn)證碼校驗(yàn)课幕,避免被濫刷厦坛,資損五垮。
5)發(fā)帖、評(píng)論杜秸、發(fā)送即時(shí)消息等用戶生成內(nèi)容的場(chǎng)景必須實(shí)現(xiàn)防刷放仗、文本內(nèi)容違禁詞過(guò)濾等風(fēng)控策略。
基本的安全意識(shí)還是要有的撬碟,一旦踩了坑诞挨,后果不堪設(shè)想莉撇。
1)數(shù)據(jù)庫(kù)表達(dá)是與否概念的字段,必須使用is_xxx的方式命名惶傻,數(shù)據(jù)類型是unsigned tinyint(1表示是棍郎,0表示否)。
2)禁用保留字银室,如desc涂佃、range、match蜈敢、delayed等辜荠,參考MySQL官方保留字。
3)主鍵索引名為pk_字段名抓狭;唯一索引名為uk_字段名伯病;普通索引名為idx_字段名。
4)varchar是可變長(zhǎng)字符串否过,不預(yù)先分配存儲(chǔ)空間午笛,長(zhǎng)度不要超過(guò)5000,如果大于此值叠纹,則選用text季研,獨(dú)立出來(lái)一張表,用主鍵來(lái)對(duì)應(yīng)誉察,避免影響其他字段索引效率与涡。
5)字段允許適當(dāng)冗余,以提高查詢性能持偏,但必須考慮數(shù)據(jù)一致性驼卖。冗余字段應(yīng)遵守:1.不是頻繁修改;2.不是varchar超長(zhǎng)字段鸿秆,更不能是text字段酌畜。
6)單表行數(shù)超過(guò)500萬(wàn)行或者單表容量超過(guò)2GB,才推薦分庫(kù)分表卿叽。
7)頁(yè)面搜索嚴(yán)禁左模糊或者全模糊桥胞,如果需要請(qǐng)走搜索引擎來(lái)解決。
8)若有order by的場(chǎng)景考婴,請(qǐng)注意利用索引的有序性贩虾。order by最后的字段是組合索引的一部分,并放在索引組合順序的最后沥阱,避免出現(xiàn)file_sort的情況缎罢,影響查詢性能。
正例:where a=? and b=? order by c; 索引:a_b_c
9)利用覆蓋索引來(lái)進(jìn)行查詢操作,避免回表策精。很形象的比喻:如果一本書(shū)需要知道第11章是什么標(biāo)題舰始,會(huì)翻開(kāi)第11章對(duì)應(yīng)的那一頁(yè)嗎?目錄(索引列)瀏覽一下就好咽袜,這個(gè)目錄就是起到覆蓋索引的目的。覆蓋索引的explain結(jié)果中及老,extra列會(huì)出現(xiàn):using index骄恶。
10)利用延遲關(guān)聯(lián)或子查詢優(yōu)化超多分頁(yè)場(chǎng)景僧鲁。說(shuō)明:MySQL并不是跳過(guò)offset行寞秃,而是取offset+N行,然后放棄前offset行绑改,返回N行厘线,那當(dāng)offset特別大的時(shí)候造壮,效率就非常低下骂束。
11)建組合索引的時(shí)候展箱,區(qū)分度最高的在最左邊。舉極端例子:如果where a=? and b=?召廷,a的列幾乎接近于唯一值竞慢,那么只需單建idx_a索引即可筹煮。
12)不要使用count(列名)或count(常量)來(lái)替代count(*)败潦,count(*)是SQL92定義的標(biāo)準(zhǔn)統(tǒng)計(jì)行數(shù)的語(yǔ)法,跟數(shù)據(jù)庫(kù)無(wú)關(guān)沟饥,跟NULL和非NULL無(wú)關(guān)贤旷。count(列名)會(huì)忽略此列為NULL值的行。
13)不得使用外鍵與級(jí)聯(lián)韧衣,一切外鍵概念必須在應(yīng)用層解決畅铭。外鍵與級(jí)聯(lián)更新適用于單機(jī)低并發(fā)萧求,不適合分布式顶瞒、高并發(fā)集群:級(jí)聯(lián)更新時(shí)強(qiáng)阻塞这难,存在數(shù)據(jù)庫(kù)更新風(fēng)暴的風(fēng)險(xiǎn)粱胜;外鍵影響數(shù)據(jù)庫(kù)的插入速度财剖。
14)數(shù)據(jù)訂正時(shí)舌镶,刪除和修改記錄時(shí)哑梳,要先select另玖,避免出現(xiàn)誤刪除袱贮,確認(rèn)無(wú)誤后才能執(zhí)行更新語(yǔ)句仿便。
15)在表查詢中嗽仪,一律不要使用*作為查詢的字段列表,需要哪些字段必須明確寫明窿凤。
16)@Transactional事務(wù)不要濫用橘沥。事務(wù)會(huì)影響數(shù)據(jù)庫(kù)的QPS,另外使用事務(wù)的地方需要考慮各方面的回滾方案夯秃,包括緩存回滾威恼、搜索引擎回滾、消息補(bǔ)償寝并、統(tǒng)計(jì)修正等箫措。
數(shù)據(jù)庫(kù)操作的一些基本常識(shí),數(shù)據(jù)庫(kù)性能變壞衬潦,多數(shù)情況是由于上層應(yīng)用的不合理使用導(dǎo)致的斤蔓。
高并發(fā)服務(wù)器建議調(diào)小TCP協(xié)議的time_wait超時(shí)時(shí)間。
說(shuō)明:操作系統(tǒng)默認(rèn)240秒后镀岛,才會(huì)關(guān)閉處于time_wait狀態(tài)的連接弦牡,在高并發(fā)訪問(wèn)下,服務(wù)器端會(huì)因?yàn)樘幱趖ime_wait的連接數(shù)太多漂羊,可能無(wú)法建立新的連接驾锰,故需要在服務(wù)器上調(diào)小此閾值。對(duì)于Linux服務(wù)器走越,變更/etc/sysctl.conf中的net.ipv4.tcp_fin_timeout
椭豫。
個(gè)人補(bǔ)充
這里補(bǔ)充一部分手冊(cè)之外的規(guī)范,一些是筆者在實(shí)際工作中遇到過(guò)旨指,實(shí)踐過(guò)的經(jīng)驗(yàn)赏酥,一些是組內(nèi)大牛分享實(shí)踐的,若有不合理的地方還請(qǐng)大家指正谆构。
1)客戶端socket超時(shí)配置應(yīng)區(qū)分連接超時(shí)和讀超時(shí)裸扶。用connect timeout控制連接建立的超時(shí)時(shí)間,用read timeout控制流讀取數(shù)據(jù)的超時(shí)時(shí)間搬素。代碼示例:
socket.connect(new InetSocketAddress(host, port), 2000); //設(shè)置連接超時(shí)為2s呵晨。
socket.setSoTimeout(10*1000); //設(shè)置讀超時(shí)為10s魏保。
2)對(duì)于QPS非常高的RPC接口,應(yīng)該將RPC客戶端socket的讀超時(shí)盡量設(shè)短摸屠,以便當(dāng)該接口不可用時(shí)谓罗,能快速超時(shí)返回,使客戶端能及時(shí)處理餐塘,避免上層應(yīng)用因此環(huán)節(jié)等待時(shí)間過(guò)長(zhǎng)而將上層服務(wù)打垮。
例如皂吮,socket.setSoTimeout(1000)
戒傻,將讀超時(shí)設(shè)置為1s。
3)數(shù)據(jù)庫(kù)查詢時(shí)蜂筹,除了order by需要利用索引的有序性需纳,對(duì)于group by操作,在數(shù)據(jù)量大時(shí)艺挪,有無(wú)利用索引的性能差異特別大不翩。
4)數(shù)據(jù)庫(kù)批量操作時(shí),要分批進(jìn)行麻裳,避免一次操作涉及記錄數(shù)過(guò)多口蝠,導(dǎo)致事務(wù)超時(shí)。
例如:根據(jù)ids批量更新數(shù)據(jù)津坑,先用Lists.partition分批拆分成多個(gè)子list妙蔗,然后每個(gè)list走一次更新,使單個(gè)事務(wù)盡快結(jié)束疆瑰,分批大小一般設(shè)置1000眉反。5)字符串分割時(shí),用Apache Commons中的StringUtils.splitPreserveAllTokens(...)代替JDK中的str.split(..)穆役,避免JDK對(duì)末尾空串的過(guò)濾導(dǎo)致結(jié)果與預(yù)期不一致寸五。
寫在最后,筆者想用阿里巴巴Java開(kāi)發(fā)手冊(cè)
的作者孤盡大神的采訪名言來(lái)結(jié)束此文:
別人都說(shuō)我們是搬磚的碼農(nóng)耿币,但我們知道自己是追求個(gè)性的藝術(shù)家梳杏。也許我們不會(huì)過(guò)多在意自己的外表和穿著,但在我們不羈的外表下淹接,骨子里追求著代碼的美秘狞、系統(tǒng)的美,代碼規(guī)范其實(shí)就是一個(gè)對(duì)程序美的定義蹈集。
與原文同步更新烁试。