《阿里巴巴 Java 開發手冊》讀書筆記

偶然看到阿里巴巴竟然出書了???趁着滿減活動(節約節約....)我趕忙買來準備看看,剛拿到的時候掂量了好多下,總以爲商家給我少發了一本書,結果打開才知道..原來這本書這麼小....html

編碼規範的重要性

別人都說咱們是搬磚的碼農,但咱們知道本身是追求個性的藝術家。也許咱們不會過多在乎本身的外表和穿着,但在咱們不羈的外表下,骨子裏追求着代碼的美、系統的美、設計的美,代碼規範其實就是一個對程序美的定義。—— 引自 序java

若是有一天在咱們的項目中看到了這樣的代碼:mysql

或者是這樣的代碼:git

這樣美不美呢?或許看着是還挺美的,可是若是須要修改,是否是人傻啦?github

那這樣的代碼呢?web

做爲一個對本身有必定要求的程序猿,是否是第一反應就是:ajax

  • 重寫!
  • 原做者是誰?錘他!

規範不一,就會像下圖中的小鴨和小雞對話同樣,語言不通,一臉囧相。雞同鴨講也偏偏形容了人與人之間溝通的痛點,自說自話,沒法達成一致意見。再舉一個生活中的例子,交通規則靠左行駛仍是靠右行駛,二者孰好孰壞並不重要,重要的是必需要在統一的方向上通行,表面上限制了自由,但其實是保障了公衆的人身安全。試想,若是沒有規定靠右行駛,那樣的路況確定擁堵不堪,險象環生。一樣,過度自由隨意、天馬行空的代碼會嚴重的傷害系統的健康,影響到可擴展性以及可維護性。sql

  • 總結:代碼規範很重要!

關於編碼規範的三大聖戰

衆所周知,互聯網公司的優點在於效率,它是企業核心競爭力。體如今產品開發領域,就是夠溝通效率和研發效率。對於溝通效率的重要性,能夠從程序猿三大 「編碼理念之爭」 提及:數據庫

  • 縮進採用空格鍵,仍是 Tab 鍵
  • if 單行語句須要大括號仍是不須要大括號
  • 左大括號不換行,仍是單獨另起一行

在美劇《硅谷》中,有這樣的一個經典鏡頭:編程

  • 程序媛:Kid? 咱們彷佛好久沒有一塊兒睡了。
  • 程序猿:如今?不可能!我永遠不會和使用空格來縮進的人睡在一塊兒!

  • 程序媛:(瘋狂敲 space 氣走了程序猿)
  • 程序猿:(甩了一句)一個 Tab 能夠代替 8個 空格!
    以後程序猿就由於視圖一步跨下八個階梯而摔了....

Tab 鍵和空格鍵的爭議確實存在,而且在知乎上討論得火熱:寫代碼時,縮進使用 tab 仍是空格?

  • 總結:使用 4 個空格好,在《阿里巴巴 Java 開發手冊》中也明確支持了這樣的作法。下面也引用一張圖來調侃一下。

if 單語句是否須要換行,也是爭論不休的話題。相對來講,寫過格式縮進類編程語言的開發者, 更加習慣於不加大括號。《手冊》中明確 if/for 單行語句必須加大括號,由於單行語句的寫法,容易在添加邏輯時引發視覺上的錯誤判斷。此外,if 不加大括號還會有局部變量做用域的問題。

左大括號是否單獨另起一行?由於 Go 語言的強制不換行,在這點上,「編程理念之爭」 的硝煙味彷佛沒有那麼濃。若是必定要給一個理由,那麼換行的代碼能夠增長一行,對於按代碼行數考覈工做量的公司員工,確定傾向於左大括號前換行。《手冊》明確左大括號不換行!

  • 總結: 其實,不少編程方式客觀上沒有對錯之分,一致性很重要,可讀性很重要,團隊溝通效率很重要。

第1章:編程規約

這一章是對傳統意義上的代碼規範,包括變量命名、代碼風格、控制語句、代碼註釋等基本的變成習慣,以及從高併發場景中提煉出來的集合處理技巧與併發多線程的注意事項。

1.1 命名風格

第一條:【強制】代碼中的命名均不能如下劃線或美圓符號開始,也不能如下劃線或美圓符號結束。

  • 反例:_name / $name / name_ / name$

儘管 $ 能夠做爲標識符使用,然而咱們應該儘可能避免對其使用。

  • 緣由: $ 一般在編譯器生成的標識符名稱中使用,若是咱們也使用這個符號,可能會有一些意想不到的錯誤發生....
  • 意想不到的錯誤示例:
package test;

public class User$VIP {
    public static void main(String[] args) {
        User user = new User();
        User.VIP vip = user.new VIP();
        vip.print();
    }
}

class User{
    class VIP{
        void print(){
            System.out.println("成員類");
        }
    }
}

仔細閱讀如下,彷佛並無什麼問題,代碼也比較簡單,但正在咱們編譯的時候,IDEA提示咱們:

定義了重複的代碼?歸根到底,都是 $ 惹的禍!由於 $ 被編譯器所使用,在源文件(.java 文件)編譯成字節碼(.class 文件)後,會稱爲頂層類型與嵌套類型之間的鏈接符。例如,若是存在一個頂層類 A,在其內聲明瞭一個成員類 B,那麼編譯以後就會產生兩個 class 文件,分別爲 A.classA$B.class

就本程序來講,會生成 3 個 class 文件(若是能夠編譯的話),分別是 User$VIP.class(頂層類)、User.classUser$VIP.class(User 類的成員類,也就是類 VIP)。因爲試圖存在兩個 User$VIP.class 因此纔會報錯!

第三至第六條:【強制】

  • 類名使用 UpperCamelCase 風格,方法名、參數名、成員變量、局部變量都贊成使用 lowerCamelCase 風格,必須聽從駝峯形式。
  • 變量命名所有大寫,單詞兼用下劃線隔開,力求予以表達完整清楚,不要嫌名字太長。

    正例:MAX_STOCK_COUNT / PRIZE_NUMBER_EVERYDAY
    反例:MAX_COUNT / PRIZE_NUMBER

  • 抽象類命名使用 Abstract 或 Base 開頭;異常類命名使用 Exception 結尾;測試類命名以它要測試的類名開始,以 Test 結尾。

第八條:【強制】 POJO 類中布爾類型的變量都不要加 is 前綴,不然部分框架解析會引發序列化錯誤。

反例:定義爲基本數據類型 Boolen isDeleted; 的屬性,它的方法名稱也是 isDeleted() ,RPC 框架在反向解析的時候,「誤覺得」 對應的屬性名稱是 deleted ,致使屬性獲取不到拋出異常。

第十二條:【推薦】 若是模塊、類、方法使用了設計模式,應在命名時體現出具體模式

  • 說明: 將設計模式體如今名字中,有利於閱讀者快速理解架構設計理念。

    正例:
    public class OrderFactory;
    public class LoginProxy;
    public class ResourceObserver;

第十三條:【推薦】 接口類中的方法和屬性不要加任何修飾符號(public 也不要加),保持代碼的間接性,並加上有效的 Javadoc 註釋。儘可能不要在接口裏定義變量,若是必定要定義變量,必須是與接口方法相關的,而且是整個應用的基礎變量。

正例:
接口方法簽名: void commit();
接口基礎變量: String COMPANY = "alibaba";
反例:
接口定義方法: public abstract void commit();

  • 說明: 若是 JDK8 中接口容許有默認實現,那麼這個 default 方法,是對全部實現類都有價值的默認實現。

第十四條:接口和實現類的命名規則

  • 1):【強制】 對於 Service 和 DAO 類,基於 SOA 的理念,暴露出來的服務必定是接口,內部的實現類用 Impl 後綴與接口區別。

正例: CacheServiceImpl 實現 CacheServcie 接口

  • 2):【推薦】 若是是形容能力的接口名稱,取對應的形容詞爲接口名(一般是 -able 的形式)。

    正例: AbstractTranslator 實現 Translatable。

1.2 常量定義

第二條:【強制】 long 或者 Long 初始賦值時,使用大寫的 L,不能是小寫的 l。小寫的 l 容易跟數字 1 混淆,形成誤解。

  • 說明: Long a = 2l; 寫得是數字的 21 仍是 Long 型的 2?

第三條:【推薦】 不要使用一個常量類維護全部變量,要按常量功能進行歸類,分開維護。

  • 說明: 大而全的變量類,非得使用查找功能才能定位到修改的常量,不利於理解和維護。

    正例:緩存相關常量放在類 CacheConsts 下;系統配置相關常量放在 ConfigConsts 下。

1.3 代碼格式

public static void main(String[] args){
    // 註釋的雙斜線與註釋內容之間有且僅有一個空格
    // 縮進 4 個空格
    String say = "hello";
    // 運算符的左右必須有 1 個空格
    int flag = 0;
    // 關鍵字 if 與括號之間必須有 1 個空格,括號內的 f與左括號、
    // 0 與右括號之間不須要空格
    if (flag == 0) {
        System.out.println(say);
    }
    // 左大括號前加空格且不換行;左大括號後換行
    if (flag == 1) {
        System.out.println("world");
    // 右大括號前換行,右大括號後有 else,不用換行
    } else {
        System.out.println("ok");
    // 在右大括號後直接結束,則必須換行
    }
}

第八條:【強制】 方法參數在定義和傳入時,多個參數逗號後邊必須加空格。

正例:下例中實參的「one」,後邊必需要有一個空格。
method("one", "two", "three");

1.4 OOP 規約

第二條:【強制】 全部的複寫方法,必須加 @Override 註解。

  • 說明: getObject() 與 get0bject() 的問題。一個是字母 O,一個是數字 0,
    加 @Override 註解能夠準確判斷是否覆蓋成功。另外,若是在抽象類中對方法簽名進行修改,其實現類會立刻編譯報錯。

打臉

第七條:【強制】 全部相同類型的包裝類對象之間值得比較,所有使用 equals 方法

  • 說明: 對於 Intergre var = ? 在 -128~127 範圍內的賦值, Integer 對象是在 IntegerCache.cache 中產生的,會複用已有的對象,這個區間內的 Integer 值能夠直接使用 == 進行判斷,可是這個區間以外的全部數據,都會在堆上產生,並不會複用已有對象。這是一個大坑,推薦使用 equals 方法進行判斷。

第十二條:【強制】 POJO 類必須寫 toString 方法。在使用 IDE 中的工具 source>generate toString 時,若是繼承了另外一個 POJO 類,注意在前面加一下 super.toString。

  • 說明: 在方法執行拋出異常時,能夠直接調用 POJO 的 toString() 方法打印其屬性值,便於排查問題。

1.5 集合處理

第七條:【強制】 不要在 foreach 循環裏進行元素的 remove / add 操做。remove 元素請使用 Iterator 方式,若是併發操做,須要對 Iterator 對象加鎖。

// 正例
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if (刪除元素的條件) {
        iterator.remove();
    }
}
// 反例
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
for (String item : list) {
    // 若是把 1 改成 2 再試一下看看是否相同
    if ("1".equals(item)) {
        list.remove(item);
    }
}

打臉

第十一條:【推薦】 高度注意 Map 類集合 K/V 能不能存儲 null 值得狀況

1.6 併發處理

第三條:【強制】 線程資源必須經過線程池提供,不容許在應用中自行顯式建立線程。

  • 說明: 使用線程池的好處是減小在建立和銷燬線程上所消耗的時間以及系統資源,解決資源不足的問題。若是不適用線程池,有可能形成系統建立大量同類線程而致使消耗完內存或者 「過渡切換」 的問題。

打臉

1.7 控制語句

第二條:【強制】 在 if / else / for / while / do 語句中,必須使用大括號。即便只有一行代碼,也應該避免採用單行的編碼方式:if (condition) statements;

第三條:【強制】 在高併發場景中,避免使用 「等於」 判斷做爲終端或退出的條件

  • 說明: 若是併發控制沒有處理好,容易產生等值判斷被 「擊穿」 的狀況,應使用大於或小於的區間判斷條件來代替。

反例:判斷剩餘獎品數量等於 0 時,終止發放獎品,但由於併發處理錯誤致使獎品數量瞬間變成了負數,這樣的話,活動沒法終止。

第四條:【推薦】 在表達異常的分支時,儘可能少用 if-else 方式

  • 說明: 若是不得不使用 if()...else if()...else... 方式表達邏輯,【強制】 避免後續代碼維護困難,請勿超過 3 層。
// 正例:超過 3 層的 if-else 邏輯判斷代碼可使用衛語句、策略模式
// 狀態模式等來實現,其中衛語句實例以下:
public void today() {
    if (isBusy()) {
        System.out.println("change time,");
        return;
    }

    if (isFree()) {
        System.out.println("go to travel.");
        return;
    }
    System.out.println("stay at home to learn Java");
    return;
}

1.8 註釋規約

第一條:【強制】 類、類屬性、類方法的註釋必須使用 Javadoc 規範,使用 /**內容*/ 格式,不得使用 //xxx 方式

第二條:【強制】 全部的抽象方法(包括接口中的方法)必需要用 Javadoc 註釋,除了返回值、參數、異常說明外,還必須指出該方法作什麼事情,實現什麼功能。

  • 說明: 對子類的實現要求,或者調用注意事項,請一併說明。

第三條:【強制】 全部的類都必須添加建立者和建立日期。

打臉

1.9 其餘

第三條:【強制】 後臺輸送給網頁的變量必須加$!{var}——中間是感嘆號

  • 說明: 若是 var=null 或者不存在,那麼 ${var} 會直接顯示在頁面上。

第四條:【強制】 注意 Math.random() 這個方法返回的是 double 類型,取值的範圍 0≤x<1(可以取到零值,注意除零異常),若是向獲取整數類型的隨機數,不要將 x 放大 10 的若干倍而後取整,直接使用 Random 對象的 nextInt 或者 nextLong 方法。

第六條:【推薦】 不要在視圖模板中加入任何複雜的邏輯。

  • 說明: 根據 MVC 理論,視圖的職責是展現,不要搶模型和控制器的工做。

第4章:安全規約

「安全生產,責任重於泰山。」 這句話一樣適用於軟件生產,本章主要說明編程中須要注意的比較基礎的安全準則。

第一條:【強制】 隸屬於用戶我的的頁面或者功能必須進行權限控制校驗

  • 說明: 放置皆有作水平權限校驗就能夠隨意訪問、修改、刪除別人的數據,好比查看他人的私信內容、修改他人的訂單。

第二條:【強制】 用戶敏感數據禁止直接展現,必須對展現數據進行脫敏。

  • 說明: 我的手機號碼會顯示爲 158****9119,隱藏中間 4 位,防止我的隱私泄露。

第三條:【強制】 用戶輸入的 SQL 參數嚴格使用參數綁定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 訪問數據庫。

打臉

第四條:【強制】 用戶請求傳入的任何參數必須作有效性驗證

  • 說明: 忽略參數校驗可能致使以下狀況。
    1)page size 過大致使內存溢出
    2)惡意 order by 致使數據庫慢查詢
    3)任意重定向
    4)SQL 注入
    5)反序列化注入
    6)正則輸入源串拒絕服務 ReDoS
    Java 代碼用正則來驗證客戶端的輸入,有些正則寫法驗證普通用戶輸入沒有問題,可是若是攻擊人員使用的是特殊構造的字符串來驗證,則有可能致使死循環。

打臉

第五條:【強制】 禁止向 HTML 頁面輸出未經安全過濾或未正確轉義的用戶數據。

第六條:【強制】 表單、AJAX 提交必須執行 CSRF 安全過濾

第七條:【強制】 在使用平臺資源,譬如短信、郵件、電話、下單、支付,必須實現正確的防重放限制,如數量限制、疲勞度控制、驗證碼校驗,避免被濫刷、資損。

  • 說明:如註冊時發送驗證碼到手機,若是沒有限制次數和頻率,那麼能夠利用此功能騷擾到其它用戶,並形成短信平臺資源浪費。

第5章:MySQL 數據庫

5.1 建表規約

第二條:【強制】 表名、字段名必須使用小寫字母或數字 , 禁止出現數字開頭,禁止兩個下劃線中間只出現數字。數據庫字段名的修改代價很大,由於沒法進行預發佈,因此字段名稱須要慎重考慮。

  • 說明: MySQL 在 Windows 下不區分大小寫,但在 Linux 下默認區分大小寫。所以,數據庫名、代表、字段名都不容許出現任何大寫字母,避免節外生枝。

    正例: getter _ admin , task _ config , level 3_ name
    反例: GetterAdmin , taskConfig , level 3 name

第四條:【強制】禁用保留字,如 desc 、 range 、 match 、 delayed 等,請參考 MySQL 官方保留字。

第五條: 【強制】主鍵索引名爲 pk_ 字段名;惟一索引名爲 uk _字段名 ; 普通索引名則爲 idx _字段名。

  • 說明: pk_ 即 primary key;uk _ 即 unique key;idx _ 即 index 的簡稱。

第六條:【強制】小數類型爲 decimal ,禁止使用 float 和 double 。

  • 說明:float 和 double 在存儲的時候,存在精度損失的問題,極可能在值的比較時,獲得不正確的結果。若是存儲的數據範圍超過 decimal 的範圍,建議將數據拆成整數和小數分開存儲。

第八條:【強制】 varchar 是可變長字符串,不預先分配存儲空間,長度不要超過 5000,若是存儲長度大於此值,定義字段類型爲 text ,獨立出來一張表,用主鍵來對應,避免影響其它字段索引效率。

第九條:【強制】表必備三字段: id , gmt _ create , gmt _ modified

  • 說明:其中 id 必爲主鍵,類型爲 unsigned bigint 、單表時自增、步長爲 1。 gmt _ create ,gmt _ modified 的類型均爲 date _ time 類型。

第十條: 【推薦】表的命名最好是加上「業務名稱_表的做用」。

正例: tiger _ task / tiger _ reader / mpp _ config

第十五條:【參考】合適的字符存儲長度,不但節約數據庫表空間、節約索引存儲,更重要的是提高檢索速度。

正例:以下表,其中無符號值能夠避免誤存負數,且擴大了表示範圍。

對象 年齡區間 類型 表示範圍
150 歲以內 unsigned tinyint 無符號值:0 到 255
數百歲 unsigned smallint 無符號值:0 到 65535
恐龍化石 數千萬年 unsigned int 無符號值:0 到約 42.9 億
太陽 約 50 億年 unsigned bigint 無符號值:0 到約 10 的 19 次方

5.2 索引規約

第五條: 【推薦】若是有 order by 的場景,請注意利用索引的有序性。 order by 最後的字段是組合索引的一部分,而且放在索引組合順序的最後,避免出現 file _ sort 的狀況,影響查詢性能。

正例: where a =? and b =? order by c; 索引: a _ b _ c
反例:索引中有範圍查找,那麼索引有序性沒法利用,如: WHERE a >10 ORDER BY b; 索引 a _ b 沒法排序。

第九條: 【推薦】建組合索引的時候,區分度最高的在最左邊。

正例:若是 where a =? and b =? , a 列的幾乎接近於惟一值,那麼只須要單建 idx _ a 索引便可。

  • 說明: 存在非等號和等號混合判斷條件時,在建索引時,請把等號條件的列前置。如: where a >? and b = ? 那麼即便 a 的區分度更高,也必須把 b 放在索引的最前列。

5.3 SQL 語句

第一條:【強制】不要使用 count( 列名 ) 或 count( 常量 ) 來替代 count( * ) , count( * ) 是 SQL 92 定義的標準統計行數的語法,跟數據庫無關,跟 NULL 和非 NULL 無關。

  • 說明: count( * ) 會統計值爲 NULL 的行,而 count( 列名 ) 不會統計此列爲 NULL 值的行。

第六條: 【強制】不得使用外鍵與級聯,一切外鍵概念必須在應用層解決。

  • 說明: ( 概念解釋 ) 學生表中的 student _ id 是主鍵,那麼成績表中的 student _ id 則爲外鍵。若是更新學生表中的 student _ id ,同時觸發成績表中的 student _ id 更新,則爲級聯更新。外鍵與級聯更新適用於單機低併發,不適合分佈式、高併發集羣 ; 級聯更新是強阻塞,存在數據庫更新風暴的風險 ; 外鍵影響數據庫的插入速度。

打臉

第八條: 【強制】數據訂正時,刪除和修改記錄時,要先 select ,避免出現誤刪除,確認無誤才能執行更新語句。

打臉

5.4 ORM 映射

整個規約對本身來講都挺有用的,由於正好涉及到這方面,幸虧感受臉不怎麼疼。

第一條:【強制】在表查詢中,一概不要使用 * 做爲查詢的字段列表,須要哪些字段必須明確寫明。

  • 說明: 1 ) 增長查詢分析器解析成本。2 ) 增減字段容易與 resultMap 配置不一致。

第二條:【強制】 POJO 類的 布爾 屬性不能加 is ,而數據庫字段必須加 is _,要求在 resultMap 中進行字段與屬性之間的映射。

  • 說明: 參見定義 POJO 類以及數據庫字段定義規定,在 中 增長映射,是必須的。在 MyBatis Generator 生成的代碼中,須要進行對應的修改。

第三條:【強制】不要用 resultClass 當返回參數,即便全部類屬性名與數據庫字段一一對應,也須要定義 ; 反過來,每個表也必然有一個與之對應。

  • 說明: 配置映射關係,使字段與 DO 類解耦,方便維護。

第七條:【強制】更新數據表記錄時,必須同時更新記錄對應的 gmt _ modified 字段值爲當前時間。

第九條:【參考】@ Transactional 事務不要濫用。事務會影響數據庫的 QPS ,另外使用事務的地方須要考慮各方面的回滾方案,包括緩存回滾、搜索引擎回滾、消息補償、統計修正等。


總結

瀏覽了一遍,仍是學習到了不少東西吧,上面也僅僅只是總結了對我本身比較收益,現階段我能吸取能實際感覺獲得的規約,若是想要 PDF 版的能夠在這裏下載:戳這裏

歡迎轉載,轉載請註明出處!
簡書ID:@我沒有三顆心臟
github:wmyskxz 歡迎關注公衆微信號:wmyskxz_javaweb 分享本身的Java Web學習之路以及各類Java學習資料

相關文章
相關標籤/搜索