谈谈Google与微信H5牛牛的Java开发规范
多年前,Google發(fā)布微信H5牛牛搭建平臺(tái)(h5.fanshubbs.com)來(lái)定義Java編碼時(shí)應(yīng)遵循的微信牛牛Q_1687054422規(guī)范;今年年初阿里則發(fā)布阿里巴巴Java 開(kāi)發(fā)手冊(cè),并隨后迭代了多個(gè)版本,直至9月份又發(fā)布了微信H5牛牛。這兩大互聯(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ī)范總覽
無(wú)規(guī)矩不成方圓,編碼規(guī)范就如同微信H5牛牛搭建平臺(tái)(h5.fanshubbs.com),有了Http、TCP等各種協(xié)議,計(jì)算機(jī)之間才能有效地通信,同樣的,有了一致的微信H5牛牛搭建平臺(tái)(h5.fanshubbs.com)編碼規(guī)范,程序員之間才能有效地合作。道理大家都懂,可現(xiàn)實(shí)中的我們,經(jīng)常一邊吐槽別人的代碼,一邊寫(xiě)著被吐槽的代碼,究其根本,就是缺乏遵從編碼規(guī)范的意識(shí)!
一、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、類(lèi)申明的規(guī)約 3、代碼格式(formatting):大括號(hào)、縮進(jìn)、換行、列長(zhǎng)限制、空格、括號(hào)、枚舉、數(shù)組、switch語(yǔ)句、注4、解、注釋、和修飾符等格式要求 5、命名(Naming):標(biāo)識(shí)符、包名、類(lèi)名、方法名、常量名、非常量成員名、參數(shù)名、局部變量的命名規(guī)范 6、編程實(shí)踐(Programming Practices):@override、異常捕獲、靜態(tài)成員、Finalizers等用法規(guī)約
二、阿里巴巴Java開(kāi)發(fā)手冊(cè)
阿里的Java開(kāi)發(fā)手冊(cè)相對(duì)于前者更上一層樓,它除了基本的編程風(fēng)格的微信H5牛牛規(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è)的寫(xiě)法,即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ù)依賴(lài)(坐標(biāo)命名、接口約定、pom配置)、服務(wù)器端各項(xiàng)配置(TCP超時(shí)、句柄數(shù)、JVM參數(shù)等)
熟知的規(guī)范
對(duì)于大家已經(jīng)爛熟于心并已習(xí)慣遵守的一些微信H5牛牛編碼規(guī)范,比如類(lèi)名、常量的命名、數(shù)組的定義、Long類(lèi)型的字面等,就不在此一一列出了,只想就一些平時(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ì)方寫(xiě)的中文注釋就各自都是亂碼了,很尷尬。對(duì)于“換行符使用Unix格式”,這個(gè)在編寫(xiě)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)不倫不類(lèi)。
[A]抽象類(lèi)命名使用Abstract或Base開(kāi)頭;異常類(lèi)使用Exception結(jié)尾;測(cè)試類(lèi)以它要測(cè)試的類(lèi)的名稱(chēng)開(kāi)始,以Test結(jié)尾
以微信H5牛牛源碼為例,其抽象類(lèi)都是以Abstract開(kāi)頭,異常類(lèi)以Exception結(jié)尾,測(cè)試類(lèi)則是以Tests結(jié)尾。
[A]POJO類(lèi)中布爾類(lèi)型的變量,都不要加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ì)誤把變量解析成activepublic?boolean?isActive()?{
??return?isActive;
}public?void?setActive(boolean?isActive)?{
??this.isActive = isActive;
}
在搞清這兩個(gè)問(wèn)題前,還是建議老老實(shí)實(shí)按規(guī)范來(lái)吧。
包名統(tǒng)一使用小寫(xiě),點(diǎn)分隔符之間有且僅有一個(gè)自然語(yǔ)義的英語(yǔ)單詞。包名統(tǒng)一使用單數(shù)形式,類(lèi)名若有復(fù)數(shù)含義,則可使用復(fù)數(shù)形式。
實(shí)際工作中看到過(guò)包名包含下劃線(xiàn)的,如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和微信H5牛牛一致的規(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ù)依賴(lài)的接口,不允許修改方法簽名,避免對(duì)接口調(diào)用方產(chǎn)生影響。作為提供方,接口過(guò)時(shí)必須加@Deprecated注解,并清晰地說(shuō)明采用的新接口或者新服務(wù)是什么;作為調(diào)用方,有義務(wù)去考證過(guò)時(shí)方法的新實(shí)現(xiàn)是什么。
接口契約,是使用方和調(diào)用方良好協(xié)作的有效保障,請(qǐng)務(wù)必遵守。
所有的相同類(lèi)型的包裝類(lèi)對(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,其他包裝類(lèi)型如微信H5牛牛、Byte等都有各自的cache。這里只提到了等值比較,對(duì)于>,<等非等值比較,沒(méi)必要手動(dòng)拆箱去比較,包裝類(lèi)型之間直接可以比較大小,親測(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ù)類(lèi)型與包裝數(shù)據(jù)類(lèi)型的使用標(biāo)準(zhǔn)如下: 1)所有的POJO類(lèi)屬性必須使用包裝數(shù)據(jù)類(lèi)型。 2)RPC方法的返回值和參數(shù)必須使用包裝數(shù)據(jù)類(lèi)型。 3)所有的局部變量使用基本數(shù)據(jù)類(lèi)型。 說(shuō)明:POJO類(lèi)屬性沒(méi)有初值是提醒使用者在需要使用時(shí),必須自己顯式地進(jìn)行賦值,任何NPE問(wèn)題,或者入口檢查,都由使用者來(lái)保證。
基本類(lèi)型作為入?yún)⒑头祷刂涤卸喾N弊病,如不情愿的默認(rèn)值,NPE風(fēng)險(xiǎn)等,除了局部變量,其他慎用。
序列化類(lèi)新增屬性時(shí),請(qǐng)不要修改serialVersionUID字段,避免反序列化失敗;如果完全不兼容升級(jí),避免反序列化混亂,那么請(qǐng)修改serialVersionUID值。
serialVersionUID是Java為每個(gè)序列化類(lèi)產(chǎn)生的版本標(biāo)識(shí):版本相同,相互之間則可序列化和反序列化;版本不同,反序列化時(shí)會(huì)拋出InvalidClassException。因不同的jdk編譯很可能會(huì)生成不同的serialVersionUID默認(rèn)值,通常需要顯式指定,如1L。
[A]final可以聲明類(lèi)、成員變量、方法、以及本地變量,下列情況使用final關(guān)鍵字: 1)不允許被繼承的類(lèi),如:String類(lèi)。 2)不允許修改引用的域?qū)ο?#xff0c;如:POJO類(lèi)的域變量。 3)不允許被重寫(xiě)的方法,如:POJO類(lèi)的setter方法。 4)不允許運(yùn)行過(guò)程中重新賦值的局部變量,如傳遞給匿名內(nèi)部類(lèi)的局部變量。
final關(guān)鍵字有諸多好處,比如JVM和Java應(yīng)用都會(huì)緩存final變量,以提高性能;final變量可在多線(xiàn)程環(huán)境下放心共享,無(wú)需額外的同步開(kāi)銷(xiāo);JVM會(huì)對(duì)final修飾的方法、變量及類(lèi)進(jìn)行優(yōu)化等,詳情可見(jiàn)深入理解Java中的final關(guān)鍵字。
慎用Object的clone方法來(lái)拷貝對(duì)象。 說(shuō)明:對(duì)象的clone方法默認(rèn)是淺拷貝,特別是引用類(lèi)型成員。若想實(shí)現(xiàn)深拷貝,需要重寫(xiě)clone方法實(shí)現(xiàn)屬性對(duì)象的拷貝。
Java中的賦值操作都是值傳遞,比如我們常用來(lái)“微信H5牛牛”DTO的工具,無(wú)論是spring的BeanUtils.copyProperties,還是Apache commons的BeanUtils.cloneBean,實(shí)際上也只是兩個(gè)DTO之間成員的引用復(fù)制,成員指向的對(duì)象還是同一個(gè),用到此類(lèi)工具的時(shí)候要有這個(gè)意識(shí),不然容易踩坑。
[A]類(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)范圍,不利于模塊解耦。
最小權(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類(lèi)型參數(shù)接受了SubList類(lèi)型的實(shí)參,則在RPC調(diào)用時(shí)會(huì)報(bào)出序列化異常。比如我們常用的guava中的Lists.partition,切分后的子list實(shí)際都是SubList類(lèi)型,在傳給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]使用工具類(lèi)Arrays.asList()把數(shù)組轉(zhuǎn)換成集合時(shí),不能使用其修改集合相關(guān)的方法,它的add/remove/clear方法會(huì)拋出UnsupportedOperationException異常。
類(lèi)似的,guava的Maps.toMap方法,返回的是一個(gè)ImmutableMap,是不可變的,不能對(duì)其調(diào)用add、remove等操作,使用時(shí)應(yīng)該有這個(gè)意識(shí)!
在JDK7版本及版本以上,Comparator必須滿(mǎn)足: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)門(mén)imSort,對(duì)于任意兩個(gè)比較元素x、y,其Comparator結(jié)果一定要是確定的,特別是對(duì)于x=y的情況,確定返回0,否則可能出現(xiàn)Comparison method violates its general contract!錯(cuò)誤。
[A]線(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和ScheduledThreadLocal:允許的創(chuàng)建線(xiàn)程數(shù)為Integer.MAX_VALUE,可能會(huì)創(chuàng)建大量的線(xiàn)程,從而導(dǎo)致OOM。
現(xiàn)在一般很少會(huì)用Executors去創(chuàng)建線(xiàn)程池了,通常會(huì)使用spring的ThreadPoolExecutorFactoryBean或者guava的MoreExecutors.listeningDecorator對(duì)前者包裝一下,對(duì)于像線(xiàn)程數(shù)、隊(duì)列大小等都是通過(guò)配置來(lái)設(shè)定。
[A]高并發(fā)時(shí),同步調(diào)用應(yīng)該去考量鎖的性能損耗。能用無(wú)鎖數(shù)據(jù)結(jié)構(gòu),就不要用鎖;能鎖區(qū)塊,就不要鎖整個(gè)方法體;能用對(duì)象鎖,就不要用類(lèi)鎖。
一句話(huà)概括就是,能不鎖就不鎖,即便鎖,也盡量使鎖的粒度最小化。
[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;
}// 接著寫(xiě)else的業(yè)務(wù)邏輯代碼;
冗長(zhǎng)的if-else可讀性差,維護(hù)困難,推薦使用衛(wèi)語(yǔ)句,邏輯清晰明了。
[A]代碼修改的同時(shí),注釋也做同步修改,尤其是參數(shù)、返回值、異常、核心邏輯等的修改。
這個(gè)在微信H5牛牛工程代碼中還真看到過(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,這條微信H5牛牛規(guī)范強(qiáng)烈推薦!
?
轉(zhuǎn)載于:https://my.oschina.net/u/3764944/blog/1604193
總結(jié)
以上是生活随笔為你收集整理的谈谈Google与微信H5牛牛的Java开发规范的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: FF300R08W2P2B11A 汽车用
- 下一篇: HDU 6078 Wavel Seque