註:本文基於阿里巴巴技術手冊的1.0.2版本編寫。
2016年底,阿里巴巴公開了其在內部使用的Java編程規範。隨後進行了幾次版本修訂,筆者當時看到的版本為v1.0.2版。下載地址可以在其官方社區——雲棲社區找到。
筆者作為一名有數年工作經驗的Java程式設計師,仔細研讀了這份手冊,覺得是一份不可多得的好材料。正如阿里巴巴在發布時所說,「阿里巴巴集團推出的《阿里巴巴Java開發手冊(正式版)》是公司近萬名開發同學集體智慧的結晶,以開發視角為中心,詳細列舉了如何開發更加高效、更加容錯、更加有協作性,力求知其然,更知其不然。結合正反例,讓Java開發者能夠提升協作效率、提高代碼質量。」 同時,阿里巴巴也期望這套Java統一規範標準將有助於提高行業編碼規範化水平,幫助行業人員提高開發質量和效率、大大降低代碼維護成本。
其實早在多年前,Google就已經把公司內部採用的所有語言的編碼規範(其稱為Style Guide)都開源在Github上。這份清單中包括了C++、Objective-C、Java、Python、R、Shell、HTML/CSS、JavaScript、AngularJS、Common Lisp、Vimscript等語言的編程規範。並且Google還發布了一個用於檢查樣式合規性的工具cpplint以及在Emacs中使用Google編程樣式的配置文件google-c-style.el。看來Google中Emacs粉比Vim粉要強勢的多。
Google為什麼要發布這樣的Style Guide呢?因為它認為幾乎所有的開源項目都需要有一組約定來規範如何編寫代碼。如果項目中的代碼都能保持一致的風格,那麼即使代碼再多也會很容易的被人理解。
(圖片來自:http://t.cn/R63jrWi)
Google的這份編程規範包含了很多方面,從」對變量使用camelCase命名法」到」絕不要使用全局變量」到」絕不允許例外「等。其Java編程規範包含7大部分,分別為介紹、源文件基本要求、源文件結構、格式化、命名、編程實踐和Javadoc。每一部分又細分為很多子條目。如果採取條規範的原因不是很容易理解,都會配有相應的示例或者引用文章。
由於Google的這份編程規範目前只有英文版本,所以在中國的程式設計師中只有少部分人知道它的存在。並且只有更少的團隊在真正的應用它,其中就包括我的團隊。我們團隊根據Google的Java style guide也演化出了自己的團隊版本,放置在團隊共享wiki上供大家隨時查閱。我們根據自身的項目特點豐富了"編程實踐"裡的內容,並且新加入一個章節來描述編寫Java代碼的一些原則,比如簡潔代碼、組合優於繼承、stream優於for循環等。
我想阿里巴巴發布的Java開發手冊之所以叫做"開發手冊",而不是像Google那樣叫做「Style Guide(樣式風格)」,是因為它不僅僅局限於style guide這一方面,而是以Java開發者為中心視角,劃分為編程規約、異常日誌規約、MYSQL規約、工程規約、安全規約五大塊,再根據內容特徵,細分成若干二級子目錄。根據約束力強弱和故障敏感性,規約依次分為強制、推薦、參考三大類。
該開發手冊中的每一條都值得了解。限於篇幅原因,這裡只列出」編程規約「中有感受的幾條來評述一下。
15.【參考】各層命名規約:
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。
命名規約的第15條描述了在Service/DAO層對於資源的操作的命名規範。這一條的參考價值極大,因為我所有待過的團隊對於這一點都沒有明顯的約束,每個團隊都有五花八門的實現。如果能遵守這一點,那麼我們在操作資源時就會減少一些困擾。
2.【強制】long或者Long初始賦值時,必須使用大寫的L,不能是小寫的l,小寫容易跟數字1混淆,造成誤解。
說明:Long a = 2l; 寫的是數字的21,還是Long型的 2?
這是常量定義的第2條。從這一點可以看出阿里巴巴對代碼可讀性的細節扣的很嚴格。我也很贊同這一點。代碼只需編寫一次,而會被查看無數次,所以要力爭在第一次編寫的時候儘可能少的引入歧義。
1.【強制】大括號的使用約定。如果是大括號內為空,則簡潔地寫成{}即可,不需要換行;如果是非空代碼塊則:
1)左大括號前不換行。
2)左大括號後換行。
3)右大括號前換行。
4)右大括號後還有else等代碼則不換行;表示終止右大括號後必須換行。
格式規約的第1條終於終結了括號之爭。這一條需要強制遵守,那麼左大括號換行一派則被徹底排除在阿里巴巴之外。有人說不推薦左大括號換行,可以減少行數,增加單個屏幕可以顯示的代碼行數。而有的人反駁說現在屏幕已經足夠大,不換行則破壞了對稱之美。其實對於我來說兩種格式都有各自的好處,我都可以接受,只要團隊能夠堅持使用其中之一即可。
5.【強制】縮進採用4個空格,禁止使用tab字符。
說明:如果使用tab縮進,必須設置1個tab為4個空格。IDEA設置tab為4個空格時,請勿勾選Use tab character;而在eclipse中,必須勾選insert spaces for tabs。
正例:(涉及1-5點)
public static void main(String[] args) {
// 縮進4個空格
String say = "hello";
// 運算符的左右必須有一個空格
int flag = 0;
// 關鍵詞if與括號之間必須有一個空格,括號內的f與左括號,0與右括號不需要空格
if (flag == 0) {
System.out.println(say);
}
// 左大括號前加空格且不換行;左大括號後換行
if (flag == 1) {
System.out.println("world");
// 右大括號前換行,右大括號後有else,不用換行
} else {
System.out.println("ok");
// 在右大括號後直接結束,則必須換行
}
}
使用空格代替tab字符進行縮進已經成為了編程界的共識。其主要原因是不同的平臺甚至不同的編輯器下tab字符的長短是不一樣的。不過Google在其《java style guide》中規定縮進為2個空格,而阿里巴巴約定為4個空格。由於4個空格的縮進比2個空格的縮進長一倍,所以如果在代碼嵌套過深的情況下可能會很快超過單行最多字符數(阿里巴巴規定為120個)的限制。不過這個問題可以從另一個方面進行思考,如果由於縮進的原因導致單行字符數超標,這很可能是代碼設計上有壞味道而導致嵌套過深。所以最好從調整代碼結構的方面下手。
6.【強制】單行字符數限制不超過120個,超出需要換行,換行時遵循如下原則:
1)第二行相對第一行縮進4個空格,從第三行開始,不再繼續縮進,參考示例。
2)運算符與下文一起換行。
3)方法調用的點符號與下文一起換行。
4)在多個參數超長,逗號後進行換行。
5)在括號前不要換行,見反例。
正例:
StringBuffer sb = new StringBuffer();
//超過 120 個字符的情況下,換行縮進 4 個空格,並且方法前的點符號一起換行
sb.append("zi").append("xin")...
.append("huang")...
.append("huang")...
.append("huang");
反例:
StringBuffer sb = new StringBuffer();
//超過 120 個字符的情況下,不要在括號前換行
sb.append("zi").append("xin")...append
("huang");
//參數很多的方法調用可能超過 120 個字符,不要在逗號前換行
method(args1, args2, args3, ...
, argsX);
關於換行,Google並沒有給出明確的要求,而阿里巴巴則給出了強制性的要求。Google特別提示可以通過一些重構手法來減少單行字符長度從而避免換行,這一點我頗為認同。
關於參數,很多方法調用超過120個字符需要換行,這暴露除了過長參數列的代碼壞味道,解決方式之一就是使用重構手法的Replace Parameter With Method的方式把一次方法調用化為多次方法調用,或者使用Introduce Parameter Object手法創造出參數對象並進行傳遞。
17.【推薦】循環體內,字符串的聯接方式,使用StringBuilder的append方法進行擴展。 反例:
String str = "start";
for (int i = 0; i < 100; i++) {
str = str + "hello";
}
說明:反編譯出的字節碼文件顯示每次循環都會new出一個StringBuilder對象,然後進行append操作,最後通過toString方法返回String對象,造成內存資源浪費。
這是《Effective Java》以及其他文章中經常提及的優化方式,而且面試初級Java工程師時幾乎是一個必考點。其實不僅是在循環體內,所有需要進行多次字符串拼接的地方都應該使用StringBuilder對象。
20.【推薦】類成員與方法訪問控制從嚴:
1)如果不允許外部直接通過 new 來創建對象,那麼構造方法必須是 private。
2)工具類不允許有public或default構造方法。
3)類非static成員變量並且與子類共享,必須是protected。
4)類非static成員變量並且僅在本類使用,必須是private。
5)類static成員變量如果僅在本類使用,必須是private。
6)若是static成員變量,必須考慮是否為final。
7)類成員方法只供類內部調用,必須是private。
8)類成員方法只對繼承類公開,那麼限制為protected。
說明:要嚴控類、方法、參數、變量的訪問範圍。過寬泛的訪問範圍不利於模塊解耦。思考:如果是一個private的方法,想刪除就刪除,可是一個public的Service方法,或者一個public的成員變量,刪除一下,不得手心冒點汗嗎?變量像自己的小孩,儘量在自己的視線內,變量作用域太大,會無限制的到處跑,那麼你會擔心的。
這其實就是經典的原則『Principle of least privilege』 的體現。我們必須遵循這一原則,但不知為何阿里巴巴將其級別列為「推薦」。
7.【參考】方法中需要進行參數校驗的場景:
1)調用頻次低的方法。
2)執行時間開銷很大的方法,參數校驗時間幾乎可以忽略不計,但如果因為參數錯誤導致 中間執行回退,或者錯誤,那得不償失。
3)需要極高穩定性和可用性的方法。
4)對外提供的開放接口,不管是RPC/API/HTTP接口。
5)敏感權限入口。
8.【參考】方法中不需要參數校驗的場景:
1)極有可能被循環調用的方法,不建議對參數進行校驗。但在方法說明裡必須註明外部參 數檢查。
2)底層的方法調用頻度都比較高,一般不校驗。畢竟是像純淨水過濾的最後一道,參數錯 誤不太可能到底層才會暴露問題。一般DAO層與Service層都在同一個應用中,部署在同一臺伺服器中,所以DAO的參數校驗,可以省略。
3)被聲明成private只會被自己代碼所調用的方法,如果能夠確定調用方法的代碼傳入參數已經做過檢查或者肯定不會有問題,此時可以不校驗參數。
編寫代碼時,對參數進行校驗是不可避免的。詳細說又扯到「防禦式編程」和「契約式編程」的話題上。這兩項之所以列為參考,並沒有強迫大家遵守。
6.【推薦】與其「半吊子」英文來注釋,不如用中文注釋把問題說清楚。專有名詞與關鍵字保持英文原文即可。
反例:「TCP連接超時」解釋成「傳輸控制協議連接超時」,理解反而費腦筋。
看到這一條我已經笑出來了。這一條說的很好,注釋是用來闡述問題的,如果看了注釋還一頭霧水,那麼這樣的注釋不要也罷。使用中文沒什麼可丟人的,解決問題才是王道。
7. 【推薦】代碼修改的同時,注釋也要進行相應的修改,尤其是參數、返回值、異常、核心邏輯等的修改。
說明:代碼與注釋更新不同步,就像路網與導航軟體更新不同步一樣,如果導航軟體嚴重滯後, 就失去了導航的意義。
阿里巴巴對該條的說明非常到位。其實我們團隊在編寫代碼時默認是沒有任何注釋的,因為我們追求的是self-explanatory methods。即代碼本身已經就能說明它的用途。只有在很少的情況下需要添加注釋。
編程規約的第九部分都是很好的tips,值得去了解和學習。
除了編程規約之外,日誌規約、MySQL規約、工程規約和安全規約也都有極高的參考價值,這也是比Google的Java Style Guide出色的地方。這裡就不再評述了。
阿里巴巴公布這個Java開發手冊絕對是值得讚賞的事情。最後我也想給其提幾點建議:
1、建議使用公開wiki的方式發布該手冊,而不是採用pdf的方式。因為如果像google那樣是公開wiki方式的話,可以方便大家參與修正和改進,並且可以看到版本歷史。
2、該手冊並沒有明確的版權許可,只是在頁腳處加入了「禁止用於商業用途,違者必究」的字樣。Google的style guide的版權為CC-By 3.0 License,建議阿里巴巴能夠指明其版權。
3、手冊中的部分示例代碼並沒有遵守其列出的編程規約,有點打臉之嫌。比如以下示例代碼:
Iterator<String> it = a.iterator();
while(it.hasNext()){
String temp = it.next();
if(刪除元素的條件){
it.remove();
}
}
其while和if關鍵字與小括號之間並沒有空格,違反了該手冊中「3.【強制】if/for/while/switch/do等保留字與左右括號之間都必須加空格。」這一規則。
4、集合處理中可以多推薦一些Java8的集合操作方法。
5、有些名詞沒有過多解釋,比如很多人可能都不知道什麼叫一方庫、二方庫。
6、希望除了這份開發手冊以外,阿里巴巴也可以推出對應的checkstyle配置文件以及Intellij、Eclipse的配置文件。畢竟格式化這些事都可以交由IDE來解決,通過在構建時使用checkstyle插件也可以防止不合規的代碼遷入到倉庫,從源頭上保證代碼樣式的一致性。
最後,希望這份Java開發手冊可以持續改進,吸納百家之長,成為每個入門程式設計師必看的手冊。
- 相關閱讀 -
Spring Batch在大型企業中的最佳實踐|洞見
以敏捷的方式運作一所大學|洞見
點擊[閱讀原文]可到ThoughtWorks官網查看原文和後續文章內容&綠字部分。
本文版權屬ThoughtWorks公司所有,如需轉載請在後臺留言聯繫。