2016年底的時候阿里巴巴公開了其在內部使用的Java編程規(guī)范。隨后進行了幾次版本修訂,目前的版本為v1.0.2版。下載地址可以在其官方社區(qū)-云棲社區(qū)https://yq.aliyun.com/articles/69327找到。

筆者作為一名有數年工作經驗的Java程序員,仔細研讀了這份手冊,覺得其是一份不可多得的好材料。阿里巴巴在發(fā)布時所說,“阿里巴巴集團推出的《阿里巴巴Java開發(fā)手冊(正式版)》是阿里巴巴近萬名開發(fā)同學集體智慧的結晶,以開發(fā)視角為中心,詳細列舉如何開發(fā)更加高效、更加容錯、更加有協作性,力求知其然,更知其不然,結合正反例,讓Java開發(fā)者能夠提升協作效率、提高代碼質量?!?同時,阿里巴巴也期望這套Java統(tǒng)一規(guī)范標準將有助于提高行業(yè)編碼規(guī)范化水平,幫助行業(yè)人員提高開發(fā)質量和效率、大大降低代碼維護成本。

其實早在多年前,Google就已經把公司內部采用的所有語言的編碼規(guī)范(其稱為Style Guide)都開源在github上,地址為https://github.com/google/styleguide。在這份清單中,包括了C++Objective-C、Java、PythonR、Shell、HTML/CSS、JavaScriptAngularJS、Common Lisp、Vimscript等語言的編程規(guī)范。并且Google還發(fā)布了一個用于檢查樣式合規(guī)性的工具cpplint以及Emacs中使用Google編程樣式的配置文件google-c-style.el??磥鞧oogle中Emacs粉比Vim粉要強勢的多。

Google為什么要發(fā)布這樣的Style Guide那?因為它認為幾乎所有的開源項目都需要有一組約定來規(guī)范如何編寫代碼。如果項目中的代碼都能保持一致的風格,那么即使代碼再多也會更容易被人理解。

Google的這份編程規(guī)范包含了很多方面,從”對變量使用camelCase命名法”到”絕不要使用全局變量”到”絕不允許例外“等。其Java編程規(guī)范包含7大部分,分別為介紹、源文件基本要求、源文件結構、格式化、命名、編程實踐和Javadoc。每一部分又細分為很多子條目。如果采取條規(guī)范的原因不是很容易理解,都會配有相應的示例或者引用文章。

由于Google的這份編程規(guī)范目前只有英文版本,所以中國的程序員只有少部分人知道它的存在。并且只有更少的團隊在真正的應用它,其中就包括我的團隊。我們團隊根據Google的Java style guide也演化出了自己的團隊版本,放置在團隊共享wiki上供大家隨時查閱。我們根據自身的項目特點豐富了”編程實踐”里的內容,并且新加入一個章節(jié)來描述編寫Java代碼的一些原則,比如簡潔代碼、組合優(yōu)于繼承、stream優(yōu)于for循環(huán)等。

我想阿里巴巴發(fā)布的Java開發(fā)手冊之所以叫做”開發(fā)手冊”,而不是像Google那樣叫做“Style Guide(樣式風格)”,是因為它不僅僅局限于style guide這一方面,而是以Java開發(fā)者為中心視角,劃分為編程規(guī)約、異常日志規(guī)約、MYSQL規(guī)約、工程規(guī)約、安全規(guī)約五大塊,再根據內容特征,細分成若干二級子目錄。根據約束力強弱和故障敏感性,規(guī)約依次分為強制、推薦、參考三大類。

該開發(fā)手冊中的每一條都值得了解。限于篇幅原因,這里只列出”編程規(guī)約“中有感受的幾條來評述一下。

15. 【參考】各層命名規(guī)約:

A) Service/DAO 層方法命名規(guī)約

1) 獲取單個對象的方法用 get 做前綴。

2) 獲取多個對象的方法用 list 做前綴。

3) 獲取統(tǒng)計值的方法用 count 做前綴。

4) 插入的方法用 save(推薦)或 insert 做前綴。

5) 刪除的方法用 remove(推薦)或 delete 做前綴。

6) 修改的方法用 update 做前綴。

B) 領域模型命名規(guī)約

1) 數據對象:xxxDO,xxx 即為數據表名。

2) 數據傳輸對象:xxxDTO,xxx 為業(yè)務領域相關的名稱。

3) 展示對象:xxxVO,xxx 一般為網頁名稱。

4) POJO 是 DO/DTO/BO/VO 的統(tǒng)稱,禁止命名成 xxxPOJO。

命名規(guī)約的第15條描述了在Service/DAO層對于資源的操作的命名規(guī)范。這一條的參考價值極大,因為我所有呆過的團隊對于這一點都沒有明顯的約束,每個團隊都有五花八門的實現。如果能遵守這一點,那么我們在操作資源時就會減少一些困擾。

2. 【強制】long 或者 Long 初始賦值時,必須使用大寫的 L,不能是小寫的 l,小寫容易跟數字1混淆,造成誤解。

說明:Long a = 2l; 寫的是數字的 21,還是 Long 型的 2?

這是常量定義的第2條。從這一點可以看出阿里巴巴對代碼可讀性的細節(jié)扣的很嚴格。我也很贊同這一點。代碼只需編寫一次,而會被查看無數次,所以要力爭在第一次編寫的時候盡可能少的引入歧義。

1. 【強制】大括號的使用約定。如果是大括號內為空,則簡潔地寫成{}即可,不需要換行;如果是非空代碼塊則:

1) 左大括號前不換行。

2) 左大括號后換行。

3) 右大括號前換行。

4) 右大括號后還有 else 等代碼則不換行;表示終止右大括號后必須換行。

格式規(guī)約的第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》中規(guī)定縮進為2個空格,而阿里巴巴約定為4個空格。由于4個空格的縮進比2個空格的縮進長一倍,所以如果在代碼嵌套過深的情況下可能會很快超過單行最多字符數(阿里巴巴規(guī)定為120個)的限制。不過這個問題可以從另一個方面進行思考,如果由于縮進的原因導致單行字符數超標,這很可能是代碼設計上有壞味道而導致嵌套過深。所以最好應該從調整代碼結構的方面下手。

6. 【強制】單行字符數限制不超過 120 個,超出需要換行,換行時遵循如下原則:

1) 第二行相對第一行縮進 4 個空格,從第三行開始,不再繼續(xù)縮進,參考示例。

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手法創(chuàng)造出參數對象并進行傳遞。

17. 【推薦】循環(huán)體內,字符串的聯接方式,使用 StringBuilder 的 append 方法進行擴展。 反例:

    String str = "start";
    for (int i = 0; i < 100; i++) {
        str = str + "hello";
    }

說明:反編譯出的字節(jié)碼文件顯示每次循環(huán)都會 new 出一個 StringBuilder 對象,然后進行append 操作,最后通過 toString 方法返回 String 對象,造成內存資源浪費。

這是《Effective Java》以及其他文章中經常提及的優(yōu)化方式,而且面試初級Java工程師時幾乎是一個必考點。其實不僅是在循環(huán)體內,而是所有需要進行多次字符串拼接的地方都應該使用StringBuilder對象。

20. 【推薦】類成員與方法訪問控制從嚴:

1) 如果不允許外部直接通過 new 來創(chuàng)建對象,那么構造方法必須是 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) 執(zhí)行時間開銷很大的方法,參數校驗時間幾乎可以忽略不計,但如果因為參數錯誤導致 中間執(zhí)行回退,或者錯誤,那得不償失。

3) 需要極高穩(wěn)定性和可用性的方法。

4) 對外?供的開放接口,不管是 RPC/API/HTTP 接口。

5) 敏感權限入口。

8. 【參考】方法中不需要參數校驗的場景:

1) 極有可能被循環(huán)調用的方法,不建議對參數進行校驗。但在方法說明里必須注明外部參 數檢查。

2) 底層的方法調用頻度都比較高,一般不校驗。畢竟是像純凈水過濾的最后一道,參數錯 誤不太可能到底層才會暴露問題。一般 DAO 層與 Service 層都在同一個應用中,部署在同一 臺服務器中,所以 DAO 的參數校驗,可以省略。

3) 被聲明成 private 只會被自己代碼所調用的方法,如果能夠確定調用方法的代碼傳入參 數已經做過檢查或者肯定不會有問題,此時可以不校驗參數。

編寫代碼時,對參數進行校驗是不可避免的。詳細說又扯到“防御式編程”和“契約式編程”的話題上。這兩項之所以列為參考,并沒有強迫大家遵守。

6. 【推薦】與其“半吊子”英文來注釋,不如用中文注釋把問題說清楚。專有名詞與關鍵字保持英文原文即可。

反例:“TCP 連接超時”解釋成“傳輸控制協議連接超時”,理解反而費腦筋。

看到這一條我已經笑出來了。這一條說的很好,注釋是用來闡述問題的,如果看了注釋還一頭霧水,那么這樣的注釋不要也罷。使用中文沒什么可丟人的,解決問題才是王道。

7. 【推薦】代碼修改的同時,注釋也要進行相應的修改,尤其是參數、返回值、異常、核心邏輯等的修改。

說明:代碼與注釋更新不同步,就像路網與導航軟件更新不同步一樣,如果導航軟件嚴重滯后, 就失去了導航的意義。

阿里巴巴對該條的說明非常到位。其實我們團隊在編寫代碼時默認是沒有任何注釋的,因為我們追求的是self-explanatory methods。即代碼本身已經就能說明它的用途。只有在很少的情況下需要添加注釋。


編程規(guī)約的第九部分都是很好的tips,值得去了解和學習。

除了編程規(guī)約之外,日志規(guī)約、MySQL規(guī)約、工程規(guī)約和安全規(guī)約也都有極高的參考價值,這也是比Google的Java Style Guide出色的地方。這里就不再評述了。


阿里巴巴公布這個Java開發(fā)手冊絕對是值得贊賞的事情。最后我也想給其提幾點建議:

  1. 建議使用公開wiki的方式發(fā)布該手冊,而不是采用pdf的方式。因為如果像google那樣是公開wiki的方式的話,可以很方便大家參與修正和改進,并且可以看到版本歷史。

  2. 該手冊并沒有明確的版權許可,只是在頁腳處加入了“禁止用于商業(yè)用途,違者必究”的字樣。Google的style guide的版權為CC-By 3.0 License,建議阿里巴巴能夠指明其版權。

  3. 手冊中的部分示例代碼并沒有遵守其列出的編程規(guī)約,有點打臉之嫌。比如以下示例代碼:

     Iterator<String> it = a.iterator();
     while(it.hasNext()){
         String temp = it.next();
         if(刪除元素的條件){
             it.remove();
         }
     }

其while和if關鍵字與小括號之間并沒有空格,違反了該手冊中3. 【強制】if/for/while/switch/do 等保留字與左右括號之間都必須加空格。這一規(guī)則。

  1. 集合處理中可以多推薦一些Java8的集合操作方法。

  2. 有些名詞沒有過多解釋,比如很多人可能都不知道什么叫一方庫、二方庫。

  3. 希望除了這份開發(fā)手冊以外,阿里巴巴也可以推出對應的checkstyle配置文件以及Intellij、Eclipse的配置文件。畢竟格式化這些事都可以交由IDE來解決,通過在構建時使用checkstyle插件也可以防止不合規(guī)的代碼遷入到倉庫,從源頭上保證代碼樣式的一致性。

最后,希望這份Java開發(fā)手冊可以持續(xù)改進,吸納百家之長,成為每個入門程序員必看的手冊。