日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

阿里Java代码规范

發(fā)布時間:2023/12/14 java 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 阿里Java代码规范 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

代碼規(guī)范

  • 一、編程規(guī)約
    • (一) 命名風格
    • (二) 常量定義
    • (三) 代碼格式
    • (四) OOP 規(guī)約
    • (五) 集合處理
    • (六) 并發(fā)處理
    • (七) 控制語句
    • (八) 注釋規(guī)約
    • (九) 其它
  • 二、異常日志
    • (一) 異常處理
    • (二) 日志規(guī)約
  • 三、單元測試
  • 四、安全規(guī)約
  • 五、MySQL 數(shù)據(jù)庫
    • (一) 建表規(guī)約
    • (二) 索引規(guī)約
    • (三) SQL 語句
    • (四) ORM 映射
  • 六、工程結(jié)構(gòu)
    • (一) 應(yīng)用分層
    • (二) 二方庫依賴
    • (三) 服務(wù)器

一、編程規(guī)約

(一) 命名風格

  • 【強制】代碼中的命名均不能以下劃線或美元符號開始,也不能以下劃線或美元符號結(jié)束。反 例 :_name / name / Object/name/nameObject / name_ / nameObject/name/?name / Object$
  • 【強制】代碼中的命名嚴禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。
    說明:正確的英文拼寫和語法可以讓閱讀者易于理解,避免歧義。注意,即使純拼音命名方式也要避免采用。
    正例:alibaba / taobao / youku / hangzhou 等國際通用的名稱,可視同英文。反例:DaZhePromotion [打折] / getPingfenByName() [評分] / int 某變量 = 3
  • 【強制】類名使用 UpperCamelCase 風格,必須遵從駝峰形式,但以下情形例外:DO / BO /
    DTO / VO / AO
    正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion
    反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
  • 【強制】方法名、參數(shù)名、成員變量、局部變量都統(tǒng)一使用 lowerCamelCase 風格,必須遵從駝峰形式。
    正例: localValue / getHttpMessage() / inputUserId
  • 【強制】常量命名全部大寫,單詞間用下劃線隔開,力求語義表達完整清楚,不要嫌名字長。正例:MAX_STOCK_COUNT
    反例:MAX_COUNT
  • 【強制】抽象類命名使用 Abstract 或 Base 開頭;異常類命名使用 Exception 結(jié)尾;測試類命名以它要測試的類的名稱開始,以 Test 結(jié)尾。
  • 【強制】中括號是數(shù)組類型的一部分,數(shù)組定義如下:String[] args;
    反例:使用 String args[]的方式來定義。
  • 【強制】POJO 類中布爾類型的變量,都不要加 is,否則部分框架解析會引起序列化錯誤。反例:定義為基本數(shù)據(jù)類型 Boolean isDeleted;的屬性,它的方法也是 isDeleted(),RPC
  • 框架在反向解析的時候,“以為”對應(yīng)的屬性名稱是 deleted,導致屬性獲取不到,進而拋出異常。
    9. 【強制】包名統(tǒng)一使用小寫,點分隔符之間有且僅有一個自然語義的英語單詞。包名統(tǒng)一使用單數(shù)形式,但是類名如果有復(fù)數(shù)含義,類名可以使用復(fù)數(shù)形式。
    正例: 應(yīng)用工具類包名為 com.alibaba.open.util、類名為 MessageUtils(此規(guī)則參考spring 的框架結(jié)構(gòu))
    10. 【強制】杜絕完全不規(guī)范的縮寫,避免望文不知義。
    反例:AbstractClass“縮寫”命名成 AbsClass;condition“縮寫”命名成 condi,此類隨意縮寫嚴重降低了代碼的可閱讀性。
    11. 【推薦】為了達到代碼自解釋的目標,任何自定義編程元素在命名時,使用盡量完整的單詞組合來表達其意。
    正例:從遠程倉庫拉取代碼的類命名為 PullCodeFromRemoteRepository。反例:變量 int a; 的隨意命名方式。
    12. 【推薦】如果模塊、接口、類、方法使用了設(shè)計模式,在命名時體現(xiàn)出具體模式。說明:將設(shè)計模式體現(xiàn)在名字中,有利于閱讀者快速理解架構(gòu)設(shè)計理念。
    正例:public class OrderFactory; public class LoginProxy;
    public class ResourceObserver;
    13. 【推薦】接口類中的方法和屬性不要加任何修飾符號(public 也不要加),保持代碼的簡潔性,并加上有效的 Javadoc 注釋。盡量不要在接口里定義變量,如果一定要定義變量,肯定是與接口方法相關(guān),并且是整個應(yīng)用的基礎(chǔ)常量。
    正例:接口方法簽名:void f();
    接口基礎(chǔ)常量表示:String COMPANY = “alibaba”;
    反例:接口方法定義:public abstract void f();
    說明:JDK8 中接口允許有默認實現(xiàn),那么這個 default 方法,是對所有實現(xiàn)類都有價值的默認實現(xiàn)。
    14. 接口和實現(xiàn)類的命名有兩套規(guī)則:
    1) 【強制】對于 Service 和 DAO 類,基于 SOA 的理念,暴露出來的服務(wù)一定是接口,內(nèi)部的實現(xiàn)類用 Impl 的后綴與接口區(qū)別。
    正例:CacheServiceImpl 實現(xiàn) CacheService 接口。
    2) 【推薦】如果是形容能力的接口名稱,取對應(yīng)的形容詞做接口名(通常是–able 的形式) 。正例:AbstractTranslator 實現(xiàn) Translatable。

  • 【參考】枚舉類名建議帶上 Enum 后綴,枚舉成員名稱需要全大寫,單詞間用下劃線隔開。說明:枚舉其實就是特殊的常量類,且構(gòu)造方法被默認強制是私有。
    正例:枚舉名字為 ProcessStatusEnum 的成員名稱:SUCCESS / UNKOWN_REASON。
  • 【參考】各層命名規(guī)約:
    A) Service/DAO 層方法命名規(guī)約
    1) 獲取單個對象的方法用 get 做前綴。
    2) 獲取多個對象的方法用 list 做前綴。
    3) 獲取統(tǒng)計值的方法用 count 做前綴。4) 插入的方法用 save/insert 做前綴。5) 刪除的方法用 remove/delete 做前綴。6) 修改的方法用 update 做前綴。
    B) 領(lǐng)域模型命名規(guī)約
    1) 數(shù)據(jù)對象:xxxDO,xxx 即為數(shù)據(jù)表名。
    2) 數(shù)據(jù)傳輸對象:xxxDTO,xxx 為業(yè)務(wù)領(lǐng)域相關(guān)的名稱。
    3) 展示對象:xxxVO,xxx 一般為網(wǎng)頁名稱。
    4) POJO 是 DO/DTO/BO/VO 的統(tǒng)稱,禁止命名成 xxxPOJO。
  • (二) 常量定義

  • 【強制】不允許任何魔法值(即未經(jīng)定義的常量)直接出現(xiàn)在代碼中。反例:String key = “Id#taobao_” + tradeId;
    cache.put(key, value);
  • 【強制】long 或者 Long 初始賦值時,使用大寫的 L,不能是小寫的 l,小寫容易跟數(shù)字 1 混淆,造成誤解。
    說明:Long a = 2l; 寫的是數(shù)字的 21,還是 Long 型的 2?
  • 【推薦】不要使用一個常量類維護所有常量,按常量功能進行歸類,分開維護。
    說明:大而全的常量類,非得使用查找功能才能定位到修改的常量,不利于理解和維護。正例:緩存相關(guān)常量放在類 CacheConsts 下;系統(tǒng)配置相關(guān)常量放在類 ConfigConsts 下。
  • 【推薦】常量的復(fù)用層次有五層:跨應(yīng)用共享常量、應(yīng)用內(nèi)共享常量、子工程內(nèi)共享常量、包內(nèi)共享常量、類內(nèi)共享常量。
    1) 跨應(yīng)用共享常量:放置在二方庫中,通常是 client.jar 中的 constant 目錄下。
    2) 應(yīng)用內(nèi)共享常量:放置在一方庫中,通常是 modules 中的 constant 目錄下。
    反例:易懂變量也要統(tǒng)一定義成應(yīng)用內(nèi)共享常量,兩位攻城師在兩個類中分別定義了表示
    “是”的變量:
  • 類 A 中:public static final String YES = “yes”;
    類 B 中 :public static final String YES = “y”; A.YES.equals(B.YES),預(yù)期是 true,但實際返回為 false,導致線上問題。
    3) 子工程內(nèi)部共享常量:即在當前子工程的 constant 目錄下。
    4) 包內(nèi)共享常量:即在當前包下單獨的 constant 目錄下。
    5) 類內(nèi)共享常量:直接在類內(nèi)部 private static final 定義。
    5. 【推薦】如果變量值僅在一個范圍內(nèi)變化,且?guī)в忻Q之外的延伸屬性,定義為枚舉類。下面正例中的數(shù)字就是延伸信息,表示星期幾。
    正例:public Enum { MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6), SUNDAY(7);}

    (三) 代碼格式

  • 【強制】大括號的使用約定。如果是大括號內(nèi)為空,則簡潔地寫成{}即可,不需要換行;如果是非空代碼塊則:
    1) 左大括號前不換行。
    2) 左大括號后換行。
    3) 右大括號前換行。
    4) 右大括號后還有 else 等代碼則不換行;表示終止的右大括號后必須換行。
  • 【強制】 左小括號和字符之間不出現(xiàn)空格;同樣,右小括號和字符之間也不出現(xiàn)空格。詳見第 5 條下方正例提示。
    反例:if (空格 a == b 空格)
  • 【強制】if/for/while/switch/do 等保留字與括號之間都必須加空格。
  • 【強制】任何二目、三目運算符的左右兩邊都需要加一個空格。
    說明:運算符包括賦值運算符=、邏輯運算符&&、加減乘除符號等。
  • 【強制】采用 4 個空格縮進,禁止使用 tab 字符。
    說明:如果使用 tab 縮進,必須設(shè)置 1 個 tab 為 4 個空格。IDEA 設(shè)置 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;
  • // 關(guān)鍵詞 if 與括號之間必須有一個空格,括號內(nèi)的 f 與左括號,0 與右括號不需要空格
    if (flag == 0) { System.out.println(say);
    }

    // 左大括號前加空格且不換行;左大括號后換行
    if (flag == 1) { System.out.println(“world”);
    // 右大括號前換行,右大括號后有 else,不用換行
    } else {
    System.out.println(“ok”);
    // 在右大括號后直接結(jié)束,則必須換行
    }
    }

  • 【強制】注釋的雙斜線與注釋內(nèi)容之間有且僅有一個空格。正例:// 注釋內(nèi)容,注意在//和注釋內(nèi)容之間有一個空格。
  • 【強制】單行字符數(shù)限制不超過 120 個,超出需要換行,換行時遵循如下原則: 1) 第二行相對第一行縮進 4 個空格,從第三行開始,不再繼續(xù)縮進,參考示例。2) 運算符與下文一起換行。
    3) 方法調(diào)用的點符號與下文一起換行。
    4) 方法調(diào)用時,多個參數(shù),需要換行時,在逗號后進行。
    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”);
    // 參數(shù)很多的方法調(diào)用可能超過 120 個字符,不要在逗號前換行
    method(args1, args2, args3, …
    , argsX);

  • 【強制】方法參數(shù)在定義和傳入時,多個參數(shù)逗號后邊必須加空格。正例:下例中實參的"a",后邊必須要有一個空格。
    method(“a”, “b”, “c”);

  • 【強制】IDE 的 text file encoding 設(shè)置為 UTF-8; IDE 中文件的換行符使用 Unix 格式, 不要使用 Windows 格式。

  • 【推薦】沒有必要增加若干空格來使某一行的字符與上一行對應(yīng)位置的字符對齊。正例:
    int a = 3; long b = 4L; float c = 5F;
    StringBuffer sb = new StringBuffer();
    說明:增加 sb 這個變量,如果需要對齊,則給 a、b、c 都要增加幾個空格,在變量比較多的情況下,是一種累贅的事情。

  • 【推薦】方法體內(nèi)的執(zhí)行語句組、變量的定義語句組、不同的業(yè)務(wù)邏輯之間或者不同的語義之間插入一個空行。相同業(yè)務(wù)邏輯和語義之間不需要插入空行。
    說明:沒有必要插入多個空行進行隔開。

  • (四) OOP 規(guī)約

  • 【強制】避免通過一個類的對象引用訪問此類的靜態(tài)變量或靜態(tài)方法,無謂增加編譯器解析成本,直接用類名來訪問即可。

  • 【強制】所有的覆寫方法,必須加@Override 注解。
    說明:getObject()與 get0bject()的問題。一個是字母的 O,一個是數(shù)字的 0,加@Override 可以準確判斷是否覆蓋成功。另外,如果在抽象類中對方法簽名進行修改,其實現(xiàn)類會馬上編譯報錯。

  • 【強制】相同參數(shù)類型,相同業(yè)務(wù)含義,才可以使用 Java 的可變參數(shù),避免使用 Object。說明:可變參數(shù)必須放置在參數(shù)列表的最后。(提倡同學們盡量不用可變參數(shù)編程)
    正例:public User getUsers(String type, Integer… ids) {…}

  • 【強制】外部正在調(diào)用或者二方庫依賴的接口,不允許修改方法簽名,避免對接口調(diào)用方產(chǎn)生影響。接口過時必須加@Deprecated 注解,并清晰地說明采用的新接口或者新服務(wù)是什么。

  • 【強制】不能使用過時的類或方法。
    說明:java.net.URLDecoder 中的方法 decode(String encodeStr) 這個方法已經(jīng)過時,應(yīng)該使用雙參數(shù) decode(String source, String encode)。接口提供方既然明確是過時接口, 那么有義務(wù)同時提供新的接口;作為調(diào)用方來說,有義務(wù)去考證過時方法的新實現(xiàn)是什么。

  • 【強制】Object 的 equals 方法容易拋空指針異常,應(yīng)使用常量或確定有值的對象來調(diào)用
    equals。
    正例:“test”.equals(object); 反例:object.equals(“test”);
    說明:推薦使用 java.util.Objects#equals(JDK7 引入的工具類)

  • 【強制】所有的相同類型的包裝類對象之間值的比較,全部使用 equals 方法比較。說明:對于 Integer var = ? 在-128 至 127 范圍內(nèi)的賦值,Integer 對象是在

  • IntegerCache.cache 產(chǎn)生,會復(fù)用已有對象,這個區(qū)間內(nèi)的 Integer 值可以直接使用==進行判斷,但是這個區(qū)間之外的所有數(shù)據(jù),都會在堆上產(chǎn)生,并不會復(fù)用已有對象,這是一個大坑, 推薦使用 equals 方法進行判斷。
    8. 關(guān)于基本數(shù)據(jù)類型與包裝數(shù)據(jù)類型的使用標準如下:
    1) 【強制】所有的 POJO 類屬性必須使用包裝數(shù)據(jù)類型。
    2) 【強制】RPC 方法的返回值和參數(shù)必須使用包裝數(shù)據(jù)類型。
    3) 【推薦】所有的局部變量使用基本數(shù)據(jù)類型。
    說明:POJO 類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何
    NPE 問題,或者入庫檢查,都由使用者來保證。
    正例:數(shù)據(jù)庫的查詢結(jié)果可能是 null,因為自動拆箱,用基本數(shù)據(jù)類型接收有 NPE 風險。反例:比如顯示成交總額漲跌情況,即正負 x%,x 為基本數(shù)據(jù)類型,調(diào)用的 RPC 服務(wù),調(diào)用不成功時,返回的是默認值,頁面顯示為 0%,這是不合理的,應(yīng)該顯示成中劃線。所以包裝數(shù)據(jù)類型的 null 值,能夠表示額外的信息,如:遠程調(diào)用失敗,異常退出。
    9. 【強制】定義 DO/DTO/VO 等 POJO 類時,不要設(shè)定任何屬性默認值。
    反例:POJO 類的 gmtCreate 默認值為 new Date();但是這個屬性在數(shù)據(jù)提取時并沒有置入具體值,在更新其它字段時又附帶更新了此字段,導致創(chuàng)建時間被修改成當前時間。
    10. 【強制】序列化類新增屬性時,請不要修改 serialVersionUID 字段,避免反序列失敗;如果完全不兼容升級,避免反序列化混亂,那么請修改 serialVersionUID 值。
    說明:注意 serialVersionUID 不一致會拋出序列化運行時異常。
    11. 【強制】構(gòu)造方法里面禁止加入任何業(yè)務(wù)邏輯,如果有初始化邏輯,請放在 init 方法中。
    12. 【強制】POJO 類必須寫 toString 方法。使用 IDE 的中工具:source> generate toString
    時,如果繼承了另一個 POJO 類,注意在前面加一下 super.toString。
    說明:在方法執(zhí)行拋出異常時,可以直接調(diào)用 POJO 的 toString()方法打印其屬性值,便于排查問題。
    13. 【推薦】使用索引訪問用 String 的 split 方法得到的數(shù)組時,需做最后一個分隔符后有無內(nèi)容的檢查,否則會有拋 IndexOutOfBoundsException 的風險。
    說明:
    String str = “a,b,c,”;
    String[] ary = str.split(",");
    // 預(yù) 期 大 于 3, 結(jié) 果 是 3 System.out.println(ary.length);
    14. 【推薦】當一個類有多個構(gòu)造方法,或者多個同名方法,這些方法應(yīng)該按順序放置在一起, 便于閱讀,此條規(guī)則優(yōu)先于第 15 條規(guī)則。
    15. 【推薦】 類內(nèi)方法定義順序依次是:公有方法或保護方法 > 私有方法 > getter/setter
    方法。

    說明:公有方法是類的調(diào)用者和維護者最關(guān)心的方法,首屏展示最好;保護方法雖然只是子類關(guān)心,也可能是“模板設(shè)計模式”下的核心方法;而私有方法外部一般不需要特別關(guān)心,是一個黑盒實現(xiàn);因為承載的信息價值較低,所有 Service 和 DAO 的 getter/setter 方法放在類體最后。
    16. 【推薦】setter 方法中,參數(shù)名稱與類成員變量名稱一致,this.成員名 = 參數(shù)名。在
    getter/setter 方法中,不要增加業(yè)務(wù)邏輯,增加排查問題的難度。反例:
    public Integer getData() { if (true) {
    return this.data + 100;
    } else {
    return this.data - 100;
    }
    }

  • 【推薦】循環(huán)體內(nèi),字符串的連接方式,使用 StringBuilder 的 append 方法進行擴展。說明:反編譯出的字節(jié)碼文件顯示每次循環(huán)都會 new 出一個 StringBuilder 對象,然后進行append 操作,最后通過 toString 方法返回 String 對象,造成內(nèi)存資源浪費。
    反例:
    String str = “start”;
    for (int i = 0; i < 100; i++) { str = str + “hello”;
    }

  • 【推薦】final 可以聲明類、成員變量、方法、以及本地變量,下列情況使用 final 關(guān)鍵字:
    1) 不允許被繼承的類,如:String 類。
    2) 不允許修改引用的域?qū)ο?#xff0c;如:POJO 類的域變量。
    3) 不允許被重寫的方法,如:POJO 類的setter 方法。
    4) 不允許運行過程中重新賦值的局部變量。
    5) 避免上下文重復(fù)使用一個變量,使用 final 描述可以強制重新定義一個變量,方便更好地進行重構(gòu)。

  • 【推薦】慎用 Object 的 clone 方法來拷貝對象。
    說明:對象的 clone 方法默認是淺拷貝,若想實現(xiàn)深拷貝需要重寫 clone 方法實現(xiàn)屬性對象的拷貝。

  • 【推薦】類成員與方法訪問控制從嚴:
    1) 如果不允許外部直接通過 new 來創(chuàng)建對象,那么構(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) 類成員方法只對繼承類公開,那么限制為 protected。
    說明:任何類、方法、參數(shù)、變量,嚴控訪問范圍。過于寬泛的訪問范圍,不利于模塊解耦。思考:如果是一個 private 的方法,想刪除就刪除,可是一個 public 的 service 方法,或者一個 public 的成員變量,刪除一下,不得手心冒點汗嗎?變量像自己的小孩,盡量在自己的視線內(nèi),變量作用域太大,無限制的到處跑,那么你會擔心的。

    (五) 集合處理

  • 【強制】關(guān)于 hashCode 和 equals 的處理,遵循如下規(guī)則:
    1) 只要重寫 equals,就必須重寫 hashCode。
    2) 因為 Set 存儲的是不重復(fù)的對象,依據(jù) hashCode 和 equals 進行判斷,所以 Set 存儲的對象必須重寫這兩個方法。
    3) 如果自定義對象做為 Map 的鍵,那么必須重寫 hashCode 和 equals。
    說明:String 重寫了hashCode 和 equals 方法,所以我們可以非常愉快地使用 String 對象作為 key 來使用。
  • 【強制】ArrayList 的subList 結(jié)果不可強轉(zhuǎn)成ArrayList,否則會拋出ClassCastException異常,即 java.util.RandomAccessSubList cannot be cast to java.util.ArrayList. 說明:subList 返回的是 ArrayList 的內(nèi)部類 SubList,并不是 ArrayList ,而是ArrayList 的一個視圖,對于 SubList 子列表的所有操作最終會反映到原列表上。
  • 【強制】在 subList 場景中,高度注意對原集合元素個數(shù)的修改,會導致子列表的遍歷、增加、刪除均會產(chǎn)生 ConcurrentModificationException 異常。
  • 【強制】使用集合轉(zhuǎn)數(shù)組的方法,必須使用集合的 toArray(T[] array),傳入的是類型完全一樣的數(shù)組,大小就是 list.size()。
    說明:使用 toArray 帶參方法,入?yún)⒎峙涞臄?shù)組空間不夠大時,toArray 方法內(nèi)部將重新分配內(nèi)存空間,并返回新數(shù)組地址;如果數(shù)組元素大于實際所需,下標為[ list.size() ]的數(shù)組元素將被置為 null,其它數(shù)組元素保持原值,因此最好將方法入?yún)?shù)組大小定義與集合元素個數(shù)一致。
    正例:
    List list = new ArrayList(2); list.add(“guan”);
    list.add(“bao”);
    String[] array = new String[list.size()]; array = list.toArray(array);
  • 反例:直接使用 toArray 無參方法存在問題,此方法返回值只能是 Object[]類,若強轉(zhuǎn)其它類型數(shù)組將出現(xiàn) ClassCastException 錯誤。
    5. 【強制】使用工具類 Arrays.asList()把數(shù)組轉(zhuǎn)換成集合時,不能使用其修改集合相關(guān)的方法,它的 add/remove/clear 方法會拋出 UnsupportedOperationException 異常。
    說明:asList 的返回對象是一個 Arrays 內(nèi)部類,并沒有實現(xiàn)集合的修改方法。Arrays.asList體現(xiàn)的是適配器模式,只是轉(zhuǎn)換接口,后臺的數(shù)據(jù)仍是數(shù)組。
    String[] str = new String[] { “you”, “wu” }; List list = Arrays.asList(str);
    第一種情況:list.add(“yangguanbao”); 運行時異常。
    第二種情況:str[0] = “gujin”; 那么 list.get(0)也會隨之修改。
    6. 【強制】泛型通配符<? extends T>來接收返回的數(shù)據(jù),此寫法的泛型集合不能使用 add 方法,而<? super T>不能使用 get 方法,做為接口調(diào)用賦值時易出錯。
    說明:擴展說一下 PECS(Producer Extends Consumer Super)原則:第一、頻繁往外讀取內(nèi)容的,適合用<? extends T>。第二、經(jīng)常往里插入的,適合用<? super T>。
    7. 【強制】不要在 foreach 循環(huán)里進行元素的remove/add 操作。remove 元素請使用 Iterator
    方式,如果并發(fā)操作,需要對 Iterator 對象加鎖。正例:
    Iterator iterator = list.iterator(); while (iterator.hasNext()) {
    String item = iterator.next(); if (刪除元素的條件) {
    iterator.remove();
    }
    }
    反例:
    List list = new ArrayList(); list.add(“1”);
    list.add(“2”);
    for (String item : list) { if (“1”.equals(item)) {
    list.remove(item);
    }
    }
    說明:以上代碼的執(zhí)行結(jié)果肯定會出乎大家的意料,那么試一下把“1”換成“2”,會是同樣的結(jié)果嗎?

  • 【強制】 在 JDK7 版本及以上,Comparator 要滿足如下三個條件,不然 Arrays.sort, Collections.sort 會報 IllegalArgumentException 異常。
    說明:三個條件如下
    1) x,y 的比較結(jié)果和 y,x 的比較結(jié)果相反。
  • 2) x>y,y>z,則x>z。
    3) x=y,則x,z 比較結(jié)果和 y,z 比較結(jié)果相同。
    反例:下例中沒有處理相等的情況,實際使用中可能會出現(xiàn)異常:
    new Comparator() {
    @Override
    public int compare(Student o1, Student o2) { return o1.getId() > o2.getId() ? 1 : -1;
    }
    };

  • 【推薦】集合初始化時,指定集合初始值大小。
    說明:HashMap 使用 HashMap(int initialCapacity) 初始化,
    正例:initialCapacity = (需要存儲的元素個數(shù) / 負載因子) + 1。注意負載因子(即 loader factor)默認為 0.75,如果暫時無法確定初始值大小,請設(shè)置為 16(即默認值)。
    反例:HashMap 需要放置 1024 個元素,由于沒有設(shè)置容量初始大小,隨著元素不斷增加,容量 7 次被迫擴大,resize 需要重建 hash 表,嚴重影響性能。
  • 【推薦】使用 entrySet 遍歷 Map 類集合KV,而不是 keySet 方式進行遍歷。
    說明:keySet 其實是遍歷了 2 次,一次是轉(zhuǎn)為 Iterator 對象,另一次是從 hashMap 中取出key 所對應(yīng)的 value。而 entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.foreach 方法。
    正例:values()返回的是 V 值集合,是一個list 集合對象;keySet()返回的是 K 值集合,是一個 Set 集合對象;entrySet()返回的是 K-V 值組合集合。
  • 【推薦】高度注意 Map 類集合 K/V 能不能存儲 null 值的情況,如下表格:
  • 集合類 Key Value Super 說明
    Hashtable 不允許為 null 不允許為 null Dictionary 線程安全
    ConcurrentHashMap 不允許為 null 不允許為 null AbstractMap 鎖分段技術(shù)(JDK8:CAS)
    TreeMap 不允許為 null 允許為 null AbstractMap 線程不安全
    HashMap 允許為 null 允許為 null AbstractMap 線程不安全
    反例: 由于 HashMap 的干擾,很多人認為 ConcurrentHashMap 是可以置入 null 值,而事實上, 存儲 null 值時會拋出 NPE 異常。
    12. 【參考】合理利用好集合的有序性(sort)和穩(wěn)定性(order),避免集合的無序性(unsort)和不穩(wěn)定性(unorder)帶來的負面影響。
    說明:有序性是指遍歷的結(jié)果是按某種比較規(guī)則依次排列的。穩(wěn)定性指集合每次遍歷的元素次序是一定的。如:ArrayList 是 order/unsort;HashMap 是 unorder/unsort;TreeSet 是order/sort。

  • 【參考】利用 Set 元素唯一的特性,可以快速對一個集合進行去重操作,避免使用 List 的
    contains 方法進行遍歷、對比、去重操作。
  • (六) 并發(fā)處理

  • 【強制】獲取單例對象需要保證線程安全,其中的方法也要保證線程安全。說明:資源驅(qū)動類、工具類、單例工廠類都需要注意。

  • 【強制】創(chuàng)建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。正例:
    public class TimerTaskThread extends Thread { public TimerTaskThread() {
    super.setName(“TimerTaskThread”);

    }

  • 【強制】線程資源必須通過線程池提供,不允許在應(yīng)用中自行顯式創(chuàng)建線程。
    說明:使用線程池的好處是減少在創(chuàng)建和銷毀線程上所花的時間以及系統(tǒng)資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統(tǒng)創(chuàng)建大量同類線程而導致消耗完內(nèi)存或者“過度切換”的問題。

  • 【強制】線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規(guī)則,規(guī)避資源耗盡的風險。
    說明:Executors 返回的線程池對象的弊端如下:
    1) FixedThreadPool 和 SingleThreadPool:
    允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。
    2) CachedThreadPool 和 ScheduledThreadPool:
    允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE,可能會創(chuàng)建大量的線程,從而導致 OOM。

  • 【強制】SimpleDateFormat 是線程不安全的類,一般不要定義為 static 變量,如果定義為
    static,必須加鎖,或者使用 DateUtils 工具類。
    正例:注意線程安全,使用 DateUtils。亦推薦如下處理:
    private static final ThreadLocal df = new ThreadLocal() {
    @Override
    protected DateFormat initialValue() {
    return new SimpleDateFormat(“yyyy-MM-dd”);
    }
    };
    說明:如果是 JDK8 的應(yīng)用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar, DateTimeFormatter 代替 SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable thread-safe。

  • 【強制】高并發(fā)時,同步調(diào)用應(yīng)該去考量鎖的性能損耗。能用無鎖數(shù)據(jù)結(jié)構(gòu),就不要用鎖;能鎖區(qū)塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。
    說明:盡可能使加鎖的代碼塊工作量盡可能的小,避免在鎖代碼塊中調(diào)用 RPC 方法。

  • 【強制】對多個資源、數(shù)據(jù)庫表、對象同時加鎖時,需要保持一致的加鎖順序,否則可能會造成死鎖。
    說明:線程一需要對表 A、B、C 依次全部加鎖后才可以進行更新操作,那么線程二的加鎖順序也必須是 A、B、C,否則可能出現(xiàn)死鎖。

  • 【強制】并發(fā)修改同一記錄時,避免更新丟失,需要加鎖。要么在應(yīng)用層加鎖,要么在緩存加鎖,要么在數(shù)據(jù)庫層使用樂觀鎖,使用 version 作為更新依據(jù)。
    說明:如果每次訪問沖突概率小于 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數(shù)不得小于 3 次。

  • 【強制】多線程并行處理定時任務(wù)時,Timer 運行多個 TimeTask 時,只要其中之一沒有捕獲拋出的異常,其它任務(wù)便會自動終止運行,使用 ScheduledExecutorService 則沒有這個問題。

  • 【推薦】使用 CountDownLatch 進行異步轉(zhuǎn)同步操作,每個線程退出前必須調(diào)用 countDown 方法,線程執(zhí)行代碼注意 catch 異常,確保 countDown 方法被執(zhí)行到,避免主線程無法執(zhí)行至 await 方法,直到超時才返回結(jié)果。
    說明:注意,子線程拋出異常堆棧,不能在主線程 try-catch 到。

  • 【推薦】避免 Random 實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一
    seed 導致的性能下降。
    說明:Random 實例包括 java.util.Random 的實例或者 Math.random()的方式。
    正例:在 JDK7 之后,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前,需要編碼保證每個線程持有一個實例。

  • 【推薦】在并發(fā)場景下,通過雙重檢查鎖(double-checked locking)實現(xiàn)延遲初始化的優(yōu)化問題隱患(可參考 The “Double-Checked Locking is Broken” Declaration),推薦解決方案中較為簡單一種(適用于 JDK5 及以上版本),將目標屬性聲明為 volatile 型。
    反例:
    class Singleton {
    private Helper helper = null; public Helper getHelper() {
    if (helper == null) synchronized(this) { if (helper == null)
    helper = new Helper();
    }
    return helper;
    }
    // other methods and fields…
    }

  • 【參考】volatile 解決多線程內(nèi)存不可見問題。對于一寫多讀,是可以解決變量同步問題, 但是如果多寫,同樣無法解決線程安全問題。如果是 count++操作,使用如下類實現(xiàn): AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是 JDK8,推薦使用 LongAdder 對象,比 AtomicLong 性能更好(減少樂觀鎖的重試次數(shù))。

  • 【參考】 HashMap 在容量不夠進行 resize 時由于高并發(fā)可能出現(xiàn)死鏈,導致 CPU 飆升,在開發(fā)過程中可以使用其它數(shù)據(jù)結(jié)構(gòu)或加鎖來規(guī)避此風險。

  • 【參考】ThreadLocal 無法解決共享對象的更新問題,ThreadLocal 對象建議使用 static 修飾。這個變量是針對一個線程內(nèi)所有操作共享的,所以設(shè)置為靜態(tài)變量,所有此類實例共享此靜態(tài)變量 ,也就是說在類第一次被使用時裝載,只分配一塊存儲空間,所有此類的對象(只要是這個線程內(nèi)定義的)都可以操控這個變量。

  • (七) 控制語句

  • 【強制】在一個 switch 塊內(nèi),每個 case 要么通過 break/return 等來終止,要么注釋說明程序?qū)⒗^續(xù)執(zhí)行到哪一個 case 為止;在一個 switch 塊內(nèi),都必須包含一個 default 語句并且放在最后,即使它什么代碼也沒有。
  • 【強制】在 if/else/for/while/do 語句中必須使用大括號。即使只有一行代碼,避免采用單行的編碼方式:if (condition) statements;
  • 【推薦】表達異常的分支時,少用 if-else 方式,這種方式可以改寫成:
    if (condition) {

    return obj;
    }
    // 接著寫 else 的業(yè)務(wù)邏輯代碼;
    說明:如果非得使用 if()…else if()…else…方式表達邏輯,【強制】避免后續(xù)代碼維護困難,請勿超過 3 層。
    正例:超過 3 層的 if-else 的邏輯判斷代碼可以使用衛(wèi)語句、策略模式、狀態(tài)模式等來實現(xiàn), 其中衛(wèi)語句示例如下:
    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 Alibaba Java Coding Guidelines.”); return;
    }

  • 【推薦】除常用方法(如 getXxx/isXxx)等外,不要在條件判斷中執(zhí)行其它復(fù)雜的語句,將復(fù)雜邏輯判斷的結(jié)果賦值給一個有意義的布爾變量名,以提高可讀性。
    說明:很多 if 語句內(nèi)的邏輯相當復(fù)雜,閱讀者需要分析條件表達式的最終結(jié)果,才能明確什么樣的條件執(zhí)行什么樣的語句,那么,如果閱讀者分析邏輯表達式錯誤呢?
    正例:
    // 偽代碼如下
    final boolean existed = (file.open(fileName, “w”) != null) && (…) || (…); if (existed) {

    }
    反例:
    if ((file.open(fileName, “w”) != null) && (…) || (…)) {

    }

  • 【推薦】循環(huán)體中的語句要考量性能,以下操作盡量移至循環(huán)體外處理,如定義對象、變量、獲取數(shù)據(jù)庫連接,進行不必要的 try-catch 操作(這個 try-catch 是否可以移至循環(huán)體外)。

  • 【推薦】接口入?yún)⒈Wo,這種場景常見的是用于做批量操作的接口。

  • 【參考】下列情形,需要進行參數(shù)校驗:
    1) 調(diào)用頻次低的方法。
    2) 執(zhí)行時間開銷很大的方法。此情形中,參數(shù)校驗時間幾乎可以忽略不計,但如果因為參數(shù)錯誤導致中間執(zhí)行回退,或者錯誤,那得不償失。
    3) 需要極高穩(wěn)定性和可用性的方法。
    4) 對外提供的開放接口,不管是 RPC/API/HTTP 接口。
    5) 敏感權(quán)限入口。

  • 【參考】下列情形,不需要進行參數(shù)校驗:
    1) 極有可能被循環(huán)調(diào)用的方法。但在方法說明里必須注明外部參數(shù)檢查要求。
    2) 底層調(diào)用頻度比較高的方法。畢竟是像純凈水過濾的最后一道,參數(shù)錯誤不太可能到底層才會暴露問題。一般 DAO 層與 Service 層都在同一個應(yīng)用中,部署在同一臺服務(wù)器中,所以 DAO 的參數(shù)校驗,可以省略。
    3) 被聲明成 private 只會被自己代碼所調(diào)用的方法,如果能夠確定調(diào)用方法的代碼傳入?yún)?shù)已經(jīng)做過檢查或者肯定不會有問題,此時可以不校驗參數(shù)。

  • (八) 注釋規(guī)約

  • 【強制】類、類屬性、類方法的注釋必須使用 Javadoc 規(guī)范,使用/*內(nèi)容/格式,不得使用
    // xxx 方式。
    說明:在 IDE 編輯窗口中,Javadoc 方式會提示相關(guān)注釋,生成 Javadoc 可以正確輸出相應(yīng)注釋;在 IDE 中,工程調(diào)用方法時,不進入方法即可懸浮提示方法、參數(shù)、返回值的意義,提高閱讀效率。

  • 【強制】所有的抽象方法(包括接口中的方法)必須要用 Javadoc 注釋、除了返回值、參數(shù)、異常說明外,還必須指出該方法做什么事情,實現(xiàn)什么功能。
    說明:對子類的實現(xiàn)要求,或者調(diào)用注意事項,請一并說明。

  • 【強制】所有的類都必須添加創(chuàng)建者和創(chuàng)建日期。

  • 【強制】方法內(nèi)部單行注釋,在被注釋語句上方另起一行,使用//注釋。方法內(nèi)部多行注釋使用/* */注釋,注意與代碼對齊。

  • 【強制】所有的枚舉類型字段必須要有注釋,說明每個數(shù)據(jù)項的用途。

  • 【推薦】與其“半吊子”英文來注釋,不如用中文注釋把問題說清楚。專有名詞與關(guān)鍵字保持英文原文即可。
    反例:“TCP 連接超時”解釋成“傳輸控制協(xié)議連接超時”,理解反而費腦筋。

  • 【推薦】代碼修改的同時,注釋也要進行相應(yīng)的修改,尤其是參數(shù)、返回值、異常、核心邏輯等的修改。
    說明:代碼與注釋更新不同步,就像路網(wǎng)與導航軟件更新不同步一樣,如果導航軟件嚴重滯后, 就失去了導航的意義。

  • 【參考】謹慎注釋掉代碼。在上方詳細說明,而不是簡單地注釋掉。如果無用,則刪除。
    說明:代碼被注釋掉有兩種可能性:1)后續(xù)會恢復(fù)此段代碼邏輯。2)永久不用。前者如果沒有備注信息,難以知曉注釋動機。后者建議直接刪掉(代碼倉庫保存了歷史代碼)。

  • 【參考】對于注釋的要求:第一、能夠準確反應(yīng)設(shè)計思想和代碼邏輯;第二、能夠描述業(yè)務(wù)含義,使別的程序員能夠迅速了解到代碼背后的信息。完全沒有注釋的大段代碼對于閱讀者形同天書,注釋是給自己看的,即使隔很長時間,也能清晰理解當時的思路;注釋也是給繼任者看的,使其能夠快速接替自己的工作。

  • 【參考】好的命名、代碼結(jié)構(gòu)是自解釋的,注釋力求精簡準確、表達到位。避免出現(xiàn)注釋的一個極端:過多過濫的注釋,代碼的邏輯一旦修改,修改注釋是相當大的負擔。
    反例:
    // put elephant into fridge put(elephant, fridge);

  • 方法名 put,加上兩個有意義的變量名 elephant 和 fridge,已經(jīng)說明了這是在干什么,語義清晰的代碼不需要額外的注釋。
    11. 【參考】特殊注釋標記,請注明標記人與標記時間。注意及時處理這些標記,通過標記掃描, 經(jīng)常清理此類標記。線上故障有時候就是來源于這些標記處的代碼。
    1) 待辦事宜(TODO):( 標記人,標記時間,[預(yù)計處理時間])
    表示需要實現(xiàn),但目前還未實現(xiàn)的功能。這實際上是一個 Javadoc 的標簽,目前的 Javadoc 還沒有實現(xiàn),但已經(jīng)被廣泛使用。只能應(yīng)用于類,接口和方法(因為它是一個 Javadoc 標簽)。2) 錯誤,不能工作(FIXME):(標記人,標記時間,[預(yù)計處理時間])
    在注釋中用 FIXME 標記某代碼是錯誤的,而且不能工作,需要及時糾正的情況。

    (九) 其它

  • 【強制】在使用正則表達式時,利用好其預(yù)編譯功能,可以有效加快正則匹配速度。說明:不要在方法體內(nèi)定義:Pattern pattern = Pattern.compile(規(guī)則);

  • 【強制】velocity 調(diào)用 POJO 類的屬性時,建議直接使用屬性名取值即可,模板引擎會自動按規(guī)范調(diào)用 POJO 的 getXxx(),如果是 boolean 基本數(shù)據(jù)類型變量(boolean 命名不需要加 is 前綴),會自動調(diào)用 isXxx()方法。
    說明:注意如果是 Boolean 包裝類對象,優(yōu)先調(diào)用 getXxx()的方法。

  • 【強制】后臺輸送給頁面的變量必須加!var——中間的感嘆號。說明:如果var=null或者不存在,那么!{var}——中間的感嘆號。 說明:如果 var=null 或者不存在,那么!var。var=null{var}會直接顯示在頁面上。

  • 【強制】注意 Math.random() 這個方法返回是 double 類型,注意取值的范圍 0≤x<1(能夠取到零值,注意除零異常),如果想獲取整數(shù)類型的隨機數(shù),不要將 x 放大 10 的若干倍然后取整,直接使用 Random 對象的 nextInt 或者 nextLong 方法。

  • 【強制】獲取當前毫秒數(shù) System.currentTimeMillis(); 而不是new Date().getTime(); 說明:如果想獲取更加精確的納秒級時間值,使用 System.nanoTime()的方式。在 JDK8 中, 針對統(tǒng)計時間等場景,推薦使用 Instant 類。

  • 【推薦】不要在視圖模板中加入任何復(fù)雜的邏輯。
    說明:根據(jù) MVC 理論,視圖的職責是展示,不要搶模型和控制器的活。

  • 【推薦】任何數(shù)據(jù)結(jié)構(gòu)的構(gòu)造或初始化,都應(yīng)指定大小,避免數(shù)據(jù)結(jié)構(gòu)無限增長吃光內(nèi)存。

  • 【推薦】及時清理不再使用的代碼段或配置信息。
    說明:對于垃圾代碼或過時配置,堅決清理干凈,避免程序過度臃腫,代碼冗余。
    正例:對于暫時被注釋掉,后續(xù)可能恢復(fù)使用的代碼片斷,在注釋代碼上方,統(tǒng)一規(guī)定使用三個斜杠(///)來說明注釋掉代碼的理由。

  • 二、異常日志

    (一) 異常處理

  • 【強制】Java 類庫中定義的一類 RuntimeException 可以通過預(yù)先檢查進行規(guī)避,而不應(yīng)該通過 catch 來處理,比如:IndexOutOfBoundsException,NullPointerException 等等。說明:無法通過預(yù)檢查的異常除外,如在解析一個外部傳來的字符串形式數(shù)字時,通過 catch NumberFormatException 來實現(xiàn)。
    正例:if (obj != null) {…}
    反例:try { obj.method() } catch (NullPointerException e) {…}

  • 【強制】異常不要用來做流程控制,條件控制,因為異常的處理效率比條件分支低。

  • 【強制】對大段代碼進行 try-catch,這是不負責任的表現(xiàn)。catch 時請分清穩(wěn)定代碼和非穩(wěn)定代碼,穩(wěn)定代碼指的是無論如何不會出錯的代碼。對于非穩(wěn)定代碼的 catch 盡可能進行區(qū)分異常類型,再做對應(yīng)的異常處理。

  • 【強制】捕獲異常是為了處理它,不要捕獲了卻什么都不處理而拋棄之,如果不想處理它,請將該異常拋給它的調(diào)用者。最外層的業(yè)務(wù)使用者,必須處理異常,將其轉(zhuǎn)化為用戶可以理解的內(nèi)容。

  • 【強制】有 try 塊放到了事務(wù)代碼中,catch 異常后,如果需要回滾事務(wù),一定要注意手動回滾事務(wù)。

  • 【強制】finally 塊必須對資源對象、流對象進行關(guān)閉,有異常也要做 try-catch。說明:如果 JDK7 及以上,可以使用 try-with-resources 方式。

  • 【強制】不能在 finally 塊中使用 return,finally 塊中的 return 返回后方法結(jié)束執(zhí)行,不會再執(zhí)行 try 塊中的 return 語句。

  • 【強制】捕獲異常與拋異常,必須是完全匹配,或者捕獲異常是拋異常的父類。說明:如果預(yù)期對方拋的是繡球,實際接到的是鉛球,就會產(chǎn)生意外情況。

  • 【推薦】方法的返回值可以為 null,不強制返回空集合,或者空對象等,必須添加注釋充分說明什么情況下會返回 null 值。調(diào)用方需要進行 null 判斷防止 NPE 問題。
    說明:本手冊明確防止 NPE 是調(diào)用者的責任。即使被調(diào)用方法返回空集合或者空對象,對調(diào)用者來說,也并非高枕無憂,必須考慮到遠程調(diào)用失敗、序列化失敗、運行時異常等場景返回null 的情況。

  • 【推薦】防止 NPE,是程序員的基本修養(yǎng),注意 NPE 產(chǎn)生的場景:
    1) 返回類型為基本數(shù)據(jù)類型,return 包裝數(shù)據(jù)類型的對象時,自動拆箱有可能產(chǎn)生 NPE。
    反例:public int f() { return Integer 對象}, 如果為null,自動解箱拋 NPE。

  • 2) 數(shù)據(jù)庫的查詢結(jié)果可能為 null。
    3) 集合里的元素即使 isNotEmpty,取出的數(shù)據(jù)元素也可能為 null。
    4) 遠程調(diào)用返回對象時,一律要求進行空指針判斷,防止 NPE。
    5) 對于 Session 中獲取的數(shù)據(jù),建議 NPE 檢查,避免空指針。
    6) 級聯(lián)調(diào)用 obj.getA().getB().getC();一連串調(diào)用,易產(chǎn)生 NPE。正例:使用 JDK8 的 Optional 類來防止 NPE 問題。
    11. 【推薦】定義時區(qū)分 unchecked / checked 異常,避免直接拋出 new RuntimeException(), 更不允許拋出 Exception 或者 Throwable,應(yīng)使用有業(yè)務(wù)含義的自定義異常。推薦業(yè)界已定義過的自定義異常,如:DAOException / ServiceException 等。
    12. 【參考】在代碼中使用“拋異?!边€是“返回錯誤碼”,對于公司外的 http/api 開放接口必須使用“錯誤碼”;而應(yīng)用內(nèi)部推薦異常拋出;跨應(yīng)用間 RPC 調(diào)用優(yōu)先考慮使用 Result 方式,封裝 isSuccess()方法、“錯誤碼”、“錯誤簡短信息”。
    說明:關(guān)于 RPC 方法返回方式使用 Result 方式的理由:
    1) 使用拋異常返回方式,調(diào)用方如果沒有捕獲到就會產(chǎn)生運行時錯誤。
    2) 如果不加棧信息,只是 new 自定義異常,加入自己的理解的 error message,對于調(diào)用端解決問題的幫助不會太多。如果加了棧信息,在頻繁調(diào)用出錯的情況下,數(shù)據(jù)序列化和傳輸?shù)男阅軗p耗也是問題。
    13. 【參考】避免出現(xiàn)重復(fù)的代碼(Don’t Repeat Yourself),即 DRY 原則。
    說明:隨意復(fù)制和粘貼代碼,必然會導致代碼的重復(fù),在以后需要修改時,需要修改所有的副本,容易遺漏。必要時抽取共性方法,或者抽象公共類,甚至是組件化。
    正例:一個類中有多個 public 方法,都需要進行數(shù)行相同的參數(shù)校驗操作,這個時候請抽取:
    private boolean checkParam(DTO dto) {…}

    (二) 日志規(guī)約

  • 【強制】應(yīng)用中不可直接使用日志系統(tǒng)(Log4j、Logback)中的 API,而應(yīng)依賴使用日志框架SLF4J 中的 API,使用門面模式的日志框架,有利于維護和各個類的日志處理方式統(tǒng)一。
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    private static final Logger logger = LoggerFactory.getLogger(Abc.class);

  • 【強制】日志文件推薦至少保存 15 天,因為有些異常具備以“周”為頻次發(fā)生的特點。

  • 【強制】應(yīng)用中的擴展日志(如打點、臨時監(jiān)控、訪問日志等)命名方式: appName_logType_logName.log。logType:日志類型,推薦分類有stats/desc/monitor/visit 等;logName:日志描述。這種命名的好處:通過文件名就可知道日志文件屬于什么應(yīng)用,什么類型,什么目的,也有利于歸類查找。

  • 正例:mppserver 應(yīng)用中單獨監(jiān)控時區(qū)轉(zhuǎn)換異常,如:
    mppserver_monitor_timeZoneConvert.log
    說明:推薦對日志進行分類,如將錯誤日志和業(yè)務(wù)日志分開存放,便于開發(fā)人員查看,也便于通過日志對系統(tǒng)進行及時監(jiān)控。
    4. 【強制】對 trace/debug/info 級別的日志輸出,必須使用條件輸出形式或者使用占位符的方式。
    說明:logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); 如果日志級別是 warn,上述日志不會打印,但是會執(zhí)行字符串拼接操作,如果 symbol 是對象,會執(zhí)行 toString()方法,浪費了系統(tǒng)資源,執(zhí)行了上述操作,最終日志卻沒有打印。
    正例:(條件)
    if (logger.isDebugEnabled()) {
    logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
    }
    正例:(占位符)
    logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);

  • 【強制】避免重復(fù)打印日志,浪費磁盤空間,務(wù)必在 log4j.xml 中設(shè)置 additivity=false。正例:
  • 【強制】異常信息應(yīng)該包括兩類信息:案發(fā)現(xiàn)場信息和異常堆棧信息。如果不處理,那么通過關(guān)鍵字 throws 往上拋出。
    正例:logger.error(各類參數(shù)或者對象 toString + “_” + e.getMessage(), e);
  • 【推薦】謹慎地記錄日志。生產(chǎn)環(huán)境禁止輸出 debug 日志;有選擇地輸出 info 日志;如果使用 warn 來記錄剛上線時的業(yè)務(wù)行為信息,一定要注意日志輸出量的問題,避免把服務(wù)器磁盤撐爆,并記得及時刪除這些觀察日志。
    說明:大量地輸出無效日志,不利于系統(tǒng)性能提升,也不利于快速定位錯誤點。記錄日志時請思考:這些日志真的有人看嗎?看到這條日志你能做什么?能不能給問題排查帶來好處?
  • 【參考】可以使用 warn 日志級別來記錄用戶輸入?yún)?shù)錯誤的情況,避免用戶投訴時,無所適從。注意日志輸出的級別,error 級別只記錄系統(tǒng)邏輯出錯、異常等重要的錯誤信息。如非必要,請不要在此場景打出 error 級別。
  • 三、單元測試

  • 【強制】好的單元測試必須遵守 AIR 原則。
    說明:單元測試在線上運行時,感覺像空氣(AIR)一樣并不存在,但在測試質(zhì)量的保障上, 卻是非常關(guān)鍵的。好的單元測試宏觀上來說,具有自動化、獨立性、可重復(fù)執(zhí)行的特點。
    ? A:Automatic(自動化)
    ? I:Independent(獨立性)
    ? R:Repeatable(可重復(fù))

  • 【強制】單元測試應(yīng)該是全自動執(zhí)行的,并且非交互式的。測試框架通常是定期執(zhí)行的,執(zhí)行過程必須完全自動化才有意義。輸出結(jié)果需要人工檢查的測試不是一個好的單元測試。單元測試中不準使用 System.out 來進行人肉驗證,必須使用 assert 來驗證。

  • 【強制】保持單元測試的獨立性。為了保證單元測試穩(wěn)定可靠且便于維護,單元測試用例之間決不能互相調(diào)用,也不能依賴執(zhí)行的先后次序。
    反例:method2 需要依賴 method1 的執(zhí)行,將執(zhí)行結(jié)果做為 method2 的輸入。

  • 【強制】單元測試是可以重復(fù)執(zhí)行的,不能受到外界環(huán)境的影響。
    說明:單元測試通常會被放到持續(xù)集成中,每次有代碼 check in 時單元測試都會被執(zhí)行。如果單測對外部環(huán)境(網(wǎng)絡(luò)、服務(wù)、中間件等)有依賴,容易導致持續(xù)集成機制的不可用。
    正例:為了不受外界環(huán)境影響,要求設(shè)計代碼時就把 SUT 的依賴改成注入,在測試時用 spring
    這樣的 DI 框架注入一個本地(內(nèi)存)實現(xiàn)或者 Mock 實現(xiàn)。

  • 【強制】對于單元測試,要保證測試粒度足夠小,有助于精確定位問題。單測粒度至多是類級別,一般是方法級別。
    說明:只有測試粒度小才能在出錯時盡快定位到出錯位置。單測不負責檢查跨類或者跨系統(tǒng)的交互邏輯,那是集成測試的領(lǐng)域。

  • 【強制】核心業(yè)務(wù)、核心應(yīng)用、核心模塊的增量代碼確保單元測試通過。
    說明:新增代碼及時補充單元測試,如果新增代碼影響了原有單元測試,請及時修正。

  • 【強制】單元測試代碼必須寫在如下工程目錄:src/test/java,不允許寫在業(yè)務(wù)代碼目錄下。說明:源碼構(gòu)建時會跳過此目錄,而單元測試框架默認是掃描此目錄。

  • 【推薦】單元測試的基本目標:語句覆蓋率達到 70%;核心模塊的語句覆蓋率和分支覆蓋率都要達到 100%
    說明:在工程規(guī)約的應(yīng)用分層中提到的 DAO 層,Manager 層,可重用度高的 Service,都應(yīng)該進行單元測試。

  • 【推薦】編寫單元測試代碼遵守 BCDE 原則,以保證被測試模塊的交付質(zhì)量。
    ? B:Border,邊界值測試,包括循環(huán)邊界、特殊取值、特殊時間點、數(shù)據(jù)順序等。
    ? C:Correct,正確的輸入,并得到預(yù)期的結(jié)果。
    ? D:Design,與設(shè)計文檔相結(jié)合,來編寫單元測試。
    ? E:Error,強制錯誤信息輸入(如:非法數(shù)據(jù)、異常流程、非業(yè)務(wù)允許輸入等),并得到預(yù)期的結(jié)果。

  • 【推薦】對于數(shù)據(jù)庫相關(guān)的查詢,更新,刪除等操作,不能假設(shè)數(shù)據(jù)庫里的數(shù)據(jù)是存在的, 或者直接操作數(shù)據(jù)庫把數(shù)據(jù)插入進去,請使用程序插入或者導入數(shù)據(jù)的方式來準備數(shù)據(jù)。
    反例:刪除某一行數(shù)據(jù)的單元測試,在數(shù)據(jù)庫中,先直接手動增加一行作為刪除目標,但是這一行新增數(shù)據(jù)并不符合業(yè)務(wù)插入規(guī)則,導致測試結(jié)果異常。

  • 【推薦】和數(shù)據(jù)庫相關(guān)的單元測試,可以設(shè)定自動回滾機制,不給數(shù)據(jù)庫造成臟數(shù)據(jù)?;蛘邔卧獪y試產(chǎn)生的數(shù)據(jù)有明確的前后綴標識。
    正例:在 RDC 內(nèi)部單元測試中,使用 RDC_UNIT_TEST_的前綴標識數(shù)據(jù)。

  • 【推薦】對于不可測的代碼建議做必要的重構(gòu),使代碼變得可測,避免為了達到測試要求而書寫不規(guī)范測試代碼。

  • 【推薦】在設(shè)計評審階段,開發(fā)人員需要和測試人員一起確定單元測試范圍,單元測試最好覆蓋所有測試用例(UC)。

  • 【推薦】單元測試作為一種質(zhì)量保障手段,不建議項目發(fā)布后補充單元測試用例,建議在項目提測前完成單元測試。

  • 【參考】為了更方便地進行單元測試,業(yè)務(wù)代碼應(yīng)避免以下情況:
    ? 構(gòu)造方法中做的事情過多。
    ? 存在過多的全局變量和靜態(tài)方法。
    ? 存在過多的外部依賴。
    ? 存在過多的條件語句。
    說明:多層條件語句建議使用衛(wèi)語句、策略模式、狀態(tài)模式等方式重構(gòu)。

  • 【參考】不要對單元測試存在如下誤解:
    ? 那是測試同學干的事情。本文是開發(fā)手冊,凡是本文內(nèi)容都是與開發(fā)同學強相關(guān)的。
    ? 單元測試代碼是多余的。汽車的整體功能與各單元部件的測試正常與否是強相關(guān)的。
    ? 單元測試代碼不需要維護。一年半載后,那么單元測試幾乎處于廢棄狀態(tài)。
    ? 單元測試與線上故障沒有辯證關(guān)系。好的單元測試能夠最大限度地規(guī)避線上故障。

  • 四、安全規(guī)約

  • 【強制】隸屬于用戶個人的頁面或者功能必須進行權(quán)限控制校驗。
    說明:防止沒有做水平權(quán)限校驗就可隨意訪問、修改、刪除別人的數(shù)據(jù),比如查看他人的私信內(nèi)容、修改他人的訂單。
  • 【強制】用戶敏感數(shù)據(jù)禁止直接展示,必須對展示數(shù)據(jù)進行脫敏。
    說明:查看個人手機號碼會顯示成:158****9119,隱藏中間 4 位,防止隱私泄露。
  • 【強制】用戶輸入的 SQL 參數(shù)嚴格使用參數(shù)綁定或者 METADATA 字段值限定,防止 SQL 注入, 禁止字符串拼接 SQL 訪問數(shù)據(jù)庫。
  • 【強制】用戶請求傳入的任何參數(shù)必須做有效性驗證。說明:忽略參數(shù)校驗可能導致:
    ? page size 過大導致內(nèi)存溢出
    ? 惡意 order by 導致數(shù)據(jù)庫慢查詢
    ? 任意重定向
    ? SQL 注入
    ? 反序列化注入
    ? 正則輸入源串拒絕服務(wù) ReDoS
    說明:Java 代碼用正則來驗證客戶端的輸入,有些正則寫法驗證普通用戶輸入沒有問題, 但是如果攻擊人員使用的是特殊構(gòu)造的字符串來驗證,有可能導致死循環(huán)的結(jié)果。
  • 【強制】禁止向 HTML 頁面輸出未經(jīng)安全過濾或未正確轉(zhuǎn)義的用戶數(shù)據(jù)。
  • 【強制】表單、AJAX 提交必須執(zhí)行 CSRF 安全過濾。
    說明:CSRF(Cross-site request forgery)跨站請求偽造是一類常見編程漏洞。對于存在CSRF 漏洞的應(yīng)用/網(wǎng)站,攻擊者可以事先構(gòu)造好 URL,只要受害者用戶一訪問,后臺便在用戶不知情情況下對數(shù)據(jù)庫中用戶參數(shù)進行相應(yīng)修改。
  • 【強制】在使用平臺資源,譬如短信、郵件、電話、下單、支付,必須實現(xiàn)正確的防重放限制, 如數(shù)量限制、疲勞度控制、驗證碼校驗,避免被濫刷、資損。
    說明:如注冊時發(fā)送驗證碼到手機,如果沒有限制次數(shù)和頻率,那么可以利用此功能騷擾到其它用戶,并造成短信平臺資源浪費。
  • 【推薦】發(fā)貼、評論、發(fā)送即時消息等用戶生成內(nèi)容的場景必須實現(xiàn)防刷、文本內(nèi)容違禁詞過濾等風控策略。
  • 五、MySQL 數(shù)據(jù)庫

    (一) 建表規(guī)約

  • 【強制】表達是與否概念的字段,必須使用 is_xxx 的方式命名,數(shù)據(jù)類型是 unsigned tinyint
    ( 1 表示是,0 表示否)。
    說明:任何字段如果為非負數(shù),必須是 unsigned。
    正例:表達邏輯刪除的字段名 is_deleted,1 表示刪除,0 表示未刪除。

  • 【強制】表名、字段名必須使用小寫字母或數(shù)字,禁止出現(xiàn)數(shù)字開頭,禁止兩個下劃線中間只出現(xiàn)數(shù)字。數(shù)據(jù)庫字段名的修改代價很大,因為無法進行預(yù)發(fā)布,所以字段名稱需要慎重考慮。說明:MySQL 在Windows 下不區(qū)分大小寫,但在 Linux 下默認是區(qū)分大小寫。因此,數(shù)據(jù)庫名、表名、字段名,都不允許出現(xiàn)任何大寫字母,避免節(jié)外生枝。
    正例:aliyun_admin,rdc_config,level3_name 反例:AliyunAdmin,rdcConfig,level_3_name

  • 【強制】表名不使用復(fù)數(shù)名詞。
    說明:表名應(yīng)該僅僅表示表里面的實體內(nèi)容,不應(yīng)該表示實體數(shù)量,對應(yīng)于 DO 類名也是單數(shù)形式,符合表達習慣。

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

  • 【強制】主鍵索引名為 pk_字段名;唯一索引名為 uk_字段名;普通索引名則為 idx_字段名。說明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的簡稱。

  • 【強制】小數(shù)類型為 decimal,禁止使用 float 和 double。
    說明:float 和 double 在存儲的時候,存在精度損失的問題,很可能在值的比較時,得到不正確的結(jié)果。如果存儲的數(shù)據(jù)范圍超過 decimal 的范圍,建議將數(shù)據(jù)拆成整數(shù)和小數(shù)分開存儲。

  • 【強制】如果存儲的字符串長度幾乎相等,使用 char 定長字符串類型。

  • 【強制】varchar 是可變長字符串,不預(yù)先分配存儲空間,長度不要超過 5000,如果存儲長度大于此值,定義字段類型為 text,獨立出來一張表,用主鍵來對應(yīng),避免影響其它字段索引效率。

  • 【強制】表必備三字段:id, gmt_create, gmt_modified。
    說明:其中 id 必為主鍵,類型為 unsigned bigint、單表時自增、步長為 1。gmt_create, gmt_modified 的類型均為 date_time 類型,前者現(xiàn)在時表示主動創(chuàng)建,后者過去分詞表示被動更新。

  • 【推薦】表的命名最好是加上“業(yè)務(wù)名稱_表的作用”。 正例:alipay_task / force_project / trade_config

  • 【推薦】庫名與應(yīng)用名稱盡量一致。

  • 【推薦】如果修改字段含義或?qū)ψ侄伪硎镜臓顟B(tài)追加時,需要及時更新字段注釋。

  • 【推薦】字段允許適當冗余,以提高查詢性能,但必須考慮數(shù)據(jù)一致。冗余字段應(yīng)遵循:
    1) 不是頻繁修改的字段。
    2) 不是 varchar 超長字段,更不能是 text 字段。
    正例:商品類目名稱使用頻率高,字段長度短,名稱基本一成不變,可在相關(guān)聯(lián)的表中冗余存儲類目名稱,避免關(guān)聯(lián)查詢。

  • 【推薦】單表行數(shù)超過 500 萬行或者單表容量超過 2GB,才推薦進行分庫分表。
    說明:如果預(yù)計三年后的數(shù)據(jù)量根本達不到這個級別,請不要在創(chuàng)建表時就分庫分表。

  • 【參考】合適的字符存儲長度,不但節(jié)約數(shù)據(jù)庫表空間、節(jié)約索引存儲,更重要的是提升檢索速度。
    正例:如下表,其中無符號值可以避免誤存負數(shù),且擴大了表示范圍。

  • 對象 年齡區(qū)間 類型 字節(jié) 表示范圍


    150 歲之內(nèi)
    unsigned tinyint
    1
    無符號值:0 到 255


    數(shù)百歲
    unsigned smallint
    2
    無符號值:0 到 65535

    恐龍化石
    數(shù)千萬年
    unsigned int
    4
    無符號值:0 到約 42.9 億

    太陽
    約 50 億年
    unsigned bigint
    8
    無符號值:0 到約 10 的 19 次方

    (二) 索引規(guī)約

  • 【強制】業(yè)務(wù)上具有唯一特性的字段,即使是多個字段的組合,也必須建成唯一索引。
    說明:不要以為唯一索引影響了 insert 速度,這個速度損耗可以忽略,但提高查找速度是明顯的;另外,即使在應(yīng)用層做了非常完善的校驗控制,只要沒有唯一索引,根據(jù)墨菲定律,必然有臟數(shù)據(jù)產(chǎn)生。
  • 【強制】超過三個表禁止 join。需要 join 的字段,數(shù)據(jù)類型必須絕對一致;多表關(guān)聯(lián)查詢時, 保證被關(guān)聯(lián)的字段需要有索引。
    說明:即使雙表 join 也要注意表索引、SQL 性能。
  • 【強制】在 varchar 字段上建立索引時,必須指定索引長度,沒必要對全字段建立索引,根據(jù)實際文本區(qū)分度決定索引長度即可。
    說明:索引的長度與區(qū)分度是一對矛盾體,一般對字符串類型數(shù)據(jù),長度為 20 的索引,區(qū)分
  • 度會高達 90%以上,可以使用 count(distinct left(列名, 索引長度))/count()的區(qū)分度來確定。
    4. 【強制】頁面搜索嚴禁左模糊或者全模糊,如果需要請走搜索引擎來解決。
    說明:索引文件具有 B-Tree 的最左前綴匹配特性,如果左邊的值未確定,那么無法使用此索引。
    5. 【推薦】如果有 order by 的場景,請注意利用索引的有序性。order by 最后的字段是組合索引的一部分,并且放在索引組合順序的最后,避免出現(xiàn) file_sort 的情況,影響查詢性能。正例:where a=? and b=? order by c; 索引:a_b_c
    反例:索引中有范圍查找,那么索引有序性無法利用,如:WHERE a>10 ORDER BY b; 索引
    a_b 無法排序。
    6. 【推薦】利用覆蓋索引來進行查詢操作,避免回表。
    說明:如果一本書需要知道第 11 章是什么標題,會翻開第 11 章對應(yīng)的那一頁嗎?目錄瀏覽一下就好,這個目錄就是起到覆蓋索引的作用。
    正例:能夠建立索引的種類:主鍵索引、唯一索引、普通索引,而覆蓋索引是一種查詢的一種效果,用 explain 的結(jié)果,extra 列會出現(xiàn):using index。
    7. 【推薦】利用延遲關(guān)聯(lián)或者子查詢優(yōu)化超多分頁場景。
    說明:MySQL 并不是跳過 offset 行,而是取 offset+N 行,然后返回放棄前 offset 行,返回N 行,那當 offset 特別大的時候,效率就非常的低下,要么控制返回的總頁數(shù),要么對超過特定閾值的頁數(shù)進行 SQL 改寫。
    正例:先快速定位需要獲取的 id 段,然后再關(guān)聯(lián):
    SELECT a. FROM 表 1 a, (select id from 表 1 where 條件 LIMIT 100000,20 ) b where a.id=b.id

  • 【推薦】SQL 性能優(yōu)化的目標:至少要達到 range 級別,要求是 ref 級別,如果可以是 consts
    最好。說明:
    1) consts 單表中最多只有一個匹配行(主鍵或者唯一索引),在優(yōu)化階段即可讀取到數(shù)據(jù)。
    2) ref 指的是使用普通的索引(normal index)。
    3) range 對索引進行范圍檢索。
    反例:explain 表的結(jié)果,type=index,索引物理文件全掃描,速度非常慢,這個 index 級別比較 range 還低,與全表掃描是小巫見大巫。

  • 【推薦】建組合索引的時候,區(qū)分度最高的在最左邊。
    正例:如果 where a=? and b=? ,a 列的幾乎接近于唯一值,那么只需要單建 idx_a 索引即可。
    說明:存在非等號和等號混合判斷條件時,在建索引時,請把等號條件的列前置。如:where a>? and b=? 那么即使a 的區(qū)分度更高,也必須把 b 放在索引的最前列。

  • 【推薦】防止因字段類型不同造成的隱式轉(zhuǎn)換,導致索引失效。

  • 【參考】創(chuàng)建索引時避免有如下極端誤解:
    1) 寧濫勿缺。認為一個查詢就需要建一個索引。
    2) 寧缺勿濫。認為索引會消耗空間、嚴重拖慢更新和新增速度。
    3) 抵制惟一索引。認為業(yè)務(wù)的惟一性一律需要在應(yīng)用層通過“先查后插”方式解決。

  • (三) SQL 語句

  • 【強制】不要使用 count(列名)或 count(常量)來替代 count(),count()是 SQL92 定義的標準統(tǒng)計行數(shù)的語法,跟數(shù)據(jù)庫無關(guān),跟 NULL 和非 NULL 無關(guān)。
    說明:count(*)會統(tǒng)計值為 NULL 的行,而 count(列名)不會統(tǒng)計此列為 NULL 值的行。

  • 【強制】count(distinct col) 計算該列除 NULL 之外的不重復(fù)行數(shù),注意 count(distinct col1, col2) 如果其中一列全為 NULL,那么即使另一列有不同的值,也返回為 0。

  • 【強制】當某一列的值全是 NULL 時,count(col)的返回結(jié)果為 0,但 sum(col)的返回結(jié)果為NULL,因此使用 sum()時需注意 NPE 問題。
    正例:可以使用如下方式來避免 sum 的 NPE 問題:SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table;

  • 【強制】使用 ISNULL()來判斷是否為 NULL 值。說明:NULL 與任何值的直接比較都為 NULL。
    1) NULL<>NULL 的返回結(jié)果是 NULL,而不是 false。
    2) NULL=NULL 的返回結(jié)果是 NULL,而不是 true。
    3) NULL<>1 的返回結(jié)果是 NULL,而不是 true。

  • 【強制】在代碼中寫分頁查詢邏輯時,若 count 為 0 應(yīng)直接返回,避免執(zhí)行后面的分頁語句。

  • 【強制】不得使用外鍵與級聯(lián),一切外鍵概念必須在應(yīng)用層解決。
    說明:以學生和成績的關(guān)系為例,學生表中的 student_id 是主鍵,那么成績表中的 student_id 則為外鍵。如果更新學生表中的 student_id,同時觸發(fā)成績表中的 student_id 更新,即為級聯(lián)更新。外鍵與級聯(lián)更新適用于單機低并發(fā),不適合分布式、高并發(fā)集群;級聯(lián)更新是強阻塞,存在數(shù)據(jù)庫更新風暴的風險;外鍵影響數(shù)據(jù)庫的插入速度。

  • 【強制】禁止使用存儲過程,存儲過程難以調(diào)試和擴展,更沒有移植性。

  • 【強制】數(shù)據(jù)訂正時,刪除和修改記錄時,要先 select,避免出現(xiàn)誤刪除,確認無誤才能執(zhí)行更新語句。

  • 【推薦】in 操作能避免則避免,若實在避免不了,需要仔細評估 in 后邊的集合元素數(shù)量,控制在 1000 個之內(nèi)。

  • 【參考】如果有全球化需要,所有的字符存儲與表示,均以 utf-8 編碼,注意字符統(tǒng)計函數(shù)的區(qū)別。
    說明:
    SELECT LENGTH(“輕松工作”); 返回為 12
    SELECT CHARACTER_LENGTH(“輕松工作”); 返回為 4
    如果需要存儲表情,那么選擇 utfmb4 來進行存儲,注意它與 utf-8 編碼的區(qū)別。

  • 【參考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系統(tǒng)和事務(wù)日志資源少,但 TRUNCATE
    無事務(wù)且不觸發(fā) trigger,有可能造成事故,故不建議在開發(fā)代碼中使用此語句。說明:TRUNCATE TABLE 在功能上與不帶 WHERE 子句的 DELETE 語句相同。

  • (四) ORM 映射

  • 【強制】在表查詢中,一律不要使用 * 作為查詢的字段列表,需要哪些字段必須明確寫明。說明:1)增加查詢分析器解析成本。2)增減字段容易與 resultMap 配置不一致。

  • 【強制】POJO 類的布爾屬性不能加 is,而數(shù)據(jù)庫字段必須加 is_,要求在 resultMap 中進行字段與屬性之間的映射。
    說明:參見定義 POJO 類以及數(shù)據(jù)庫字段定義規(guī)定,在中增加映射,是必須的。在 MyBatis Generator 生成的代碼中,需要進行對應(yīng)的修改。

  • 【強制】不要用 resultClass 當返回參數(shù),即使所有類屬性名與數(shù)據(jù)庫字段一一對應(yīng),也需要定義;反過來,每一個表也必然有一個與之對應(yīng)。
    說明:配置映射關(guān)系,使字段與 DO 類解耦,方便維護。

  • 【強制】sql.xml 配置參數(shù)使用:#{},#param# 不要使用${} 此種方式容易出現(xiàn) SQL 注入。

  • 【強制】iBATIS 自帶的 queryForList(String statementName,int start,int size)不推薦使用。
    說明:其實現(xiàn)方式是在數(shù)據(jù)庫取到statementName 對應(yīng)的SQL 語句的所有記錄,再通過subList
    取 start,size 的子集合。
    正例:Map<String, Object> map = new HashMap<String, Object>(); map.put(“start”, start);
    map.put(“size”, size);

  • 【強制】不允許直接拿 HashMap 與 Hashtable 作為查詢結(jié)果集的輸出。
    說明:resultClass=”Hashtable”,會置入字段名和屬性值,但是值的類型不可控。

  • 【強制】更新數(shù)據(jù)表記錄時,必須同時更新記錄對應(yīng)的 gmt_modified 字段值為當前時間。

  • 【推薦】不要寫一個大而全的數(shù)據(jù)更新接口。傳入為 POJO 類,不管是不是自己的目標更新字段,都進行 update table set c1=value1,c2=value2,c3=value3; 這是不對的。執(zhí)行 SQL 時,不要更新無改動的字段,一是易出錯;二是效率低;三是增加 binlog 存儲。

  • 【參考】@Transactional 事務(wù)不要濫用。事務(wù)會影響數(shù)據(jù)庫的 QPS,另外使用事務(wù)的地方需要考慮各方面的回滾方案,包括緩存回滾、搜索引擎回滾、消息補償、統(tǒng)計修正等。

  • 【參考】中的 compareValue 是與屬性值對比的常量,一般是數(shù)字,表示相等時帶上此條件;表示不為空且不為 null 時執(zhí)行;表示不為 null 值時執(zhí)行。

  • 六、工程結(jié)構(gòu)

    (一) 應(yīng)用分層

  • 【推薦】圖中默認上層依賴于下層,箭頭關(guān)系表示可直接依賴,如:開放接口層可以依賴于
    Web 層,也可以直接依賴于 Service 層,依此類推:
  • ? 開放接口層:可直接封裝 Service 方法暴露成 RPC 接口;通過 Web 封裝成 http 接口;進行網(wǎng)關(guān)安全控制、流量控制等。
    ? 終端顯示層:各個端的模板渲染并執(zhí)行顯示的層。當前主要是 velocity 渲染,JS 渲染,
    JSP 渲染,移動端展示等。
    ? Web 層:主要是對訪問控制進行轉(zhuǎn)發(fā),各類基本參數(shù)校驗,或者不復(fù)用的業(yè)務(wù)簡單處理等。
    ? Service 層:相對具體的業(yè)務(wù)邏輯服務(wù)層。
    ? Manager 層:通用業(yè)務(wù)處理層,它有如下特征:
    1) 對第三方平臺封裝的層,預(yù)處理返回結(jié)果及轉(zhuǎn)化異常信息;
    2) 對 Service 層通用能力的下沉,如緩存方案、中間件通用處理;
    3) 與 DAO 層交互,對多個 DAO 的組合復(fù)用。
    ? DAO 層:數(shù)據(jù)訪問層,與底層 MySQL、Oracle、Hbase 等進行數(shù)據(jù)交互。
    ? 外部接口或第三方平臺:包括其它部門 RPC 開放接口,基礎(chǔ)平臺,其它公司的 HTTP 接口。

  • 【參考】 (分層異常處理規(guī)約)在 DAO 層,產(chǎn)生的異常類型有很多,無法用細粒度的異常進行 catch,使用 catch(Exception e)方式,并 throw new DAOException(e),不需要打印日志,因為日志在 Manager/Service 層一定需要捕獲并打到日志文件中去,如果同臺服務(wù)器再打日志,浪費性能和存儲。在 Service 層出現(xiàn)異常時,必須記錄出錯日志到磁盤,盡可能帶上參數(shù)信息,相當于保護案發(fā)現(xiàn)場。如果 Manager 層與 Service 同機部署,日志方式與 DAO 層處理一致,如果是單獨部署,則采用與 Service 一致的處理方式。Web 層絕不應(yīng)該繼續(xù)往上
  • 拋異常,因為已經(jīng)處于頂層,如果意識到這個異常將導致頁面無法正常渲染,那么就應(yīng)該直接跳轉(zhuǎn)到友好錯誤頁面,加上用戶容易理解的錯誤提示信息。開放接口層要將異常處理成錯誤碼和錯誤信息方式返回。
    3. 【參考】分層領(lǐng)域模型規(guī)約:
    ? DO(Data Object):與數(shù)據(jù)庫表結(jié)構(gòu)一一對應(yīng),通過 DAO 層向上傳輸數(shù)據(jù)源對象。
    ? DTO(Data Transfer Object):數(shù)據(jù)傳輸對象,Service 或 Manager 向外傳輸?shù)膶ο蟆?br /> ? BO(Business Object):業(yè)務(wù)對象。由 Service 層輸出的封裝業(yè)務(wù)邏輯的對象。
    ? AO(Application Object):應(yīng)用對象。在 Web 層與 Service 層之間抽象的復(fù)用對象模型, 極為貼近展示層,復(fù)用度不高。
    ? VO(View Object):顯示層對象,通常是 Web 向模板渲染引擎層傳輸?shù)膶ο蟆?br /> ? Query:數(shù)據(jù)查詢對象,各層接收上層的查詢請求。注意超過 2 個參數(shù)的查詢封裝,禁止使用 Map 類來傳輸。

    (二) 二方庫依賴

  • 【強制】定義 GAV 遵從以下規(guī)則:
    1) GroupID 格式:com.{公司/BU }.業(yè)務(wù)線.[子業(yè)務(wù)線],最多 4 級。
    說明:{公司/BU} 例如:alibaba/taobao/tmall/aliexpress 等 BU 一級;子業(yè)務(wù)線可選。正例:com.taobao.jstorm 或 com.alibaba.dubbo.register
    2) ArtifactID 格式:產(chǎn)品線名-模塊名。語義不重復(fù)不遺漏,先到中央倉庫去查證一下。正例:dubbo-client / fastjson-api / jstorm-tool
    3) Version:詳細規(guī)定參考下方。

  • 【強制】二方庫版本號命名方式:主版本號.次版本號.修訂號
    1) 主版本號:產(chǎn)品方向改變,或者大規(guī)模 API 不兼容,或者架構(gòu)不兼容升級。
    2) 次版本號:保持相對兼容性,增加主要功能特性,影響范圍極小的 API 不兼容修改。
    3) 修訂號:保持完全兼容性,修復(fù) BUG、新增次要功能特性等。
    說明:注意起始版本號必須為:1.0.0,而不是 0.0.1 正式發(fā)布的類庫必須先去中央倉庫進行查證,使版本號有延續(xù)性,正式版本號不允許覆蓋升級。如當前版本:1.3.3,那么下一個合理的版本號:1.3.4 或 1.4.0 或 2.0.0

  • 【強制】線上應(yīng)用不要依賴 SNAPSHOT 版本(安全包除外)。
    說明:不依賴 SNAPSHOT 版本是保證應(yīng)用發(fā)布的冪等性。另外,也可以加快編譯時的打包構(gòu)建。

  • 【強制】二方庫的新增或升級,保持除功能點之外的其它 jar 包仲裁結(jié)果不變。如果有改變, 必須明確評估和驗證,建議進行 dependency:resolve 前后信息比對,如果仲裁結(jié)果完全不一致,那么通過 dependency:tree 命令,找出差異點,進行排除 jar 包。

  • 【強制】二方庫里可以定義枚舉類型,參數(shù)可以使用枚舉類型,但是接口返回值不允許使用枚舉類型或者包含枚舉類型的 POJO 對象。

  • 【強制】依賴于一個二方庫群時,必須定義一個統(tǒng)一的版本變量,避免版本號不一致。
    說明:依賴 springframework-core,-context,-beans,它們都是同一個版本,可以定義一個變量來保存版本:${spring.version},定義依賴的時候,引用該版本。

  • 【強制】禁止在子項目的 pom 依賴中出現(xiàn)相同的 GroupId,相同的 ArtifactId,但是不同的Version。
    說明:在本地調(diào)試時會使用各子項目指定的版本號,但是合并成一個 war,只能有一個版本號出現(xiàn)在最后的 lib 目錄中。可能出現(xiàn)線下調(diào)試是正確的,發(fā)布到線上卻出故障的問題。

  • 【推薦】所有 pom 文件中的依賴聲明放在語句塊中,所有版本仲裁放在
    語句塊中。
    說明:里只是聲明版本,并不實現(xiàn)引入,因此子項目需要顯式的聲明依賴,version 和 scope 都讀取自父 pom。而所有聲明在主 pom 的
    里的依賴都會自動引入,并默認被所有的子項目繼承。

  • 【推薦】二方庫不要有配置項,最低限度不要再增加配置項。

  • 【參考】為避免應(yīng)用二方庫的依賴沖突問題,二方庫發(fā)布者應(yīng)當遵循以下原則:
    1) 精簡可控原則。移除一切不必要的 API 和依賴,只包含 Service API、必要的領(lǐng)域模型對象、Utils 類、常量、枚舉等。如果依賴其它二方庫,盡量是 provided 引入,讓二方庫使用
    者去依賴具體版本號;無 log 具體實現(xiàn),只依賴日志框架。
    2) 穩(wěn)定可追溯原則。每個版本的變化應(yīng)該被記錄,二方庫由誰維護,源碼在哪里,都需要能方便查到。除非用戶主動升級版本,否則公共二方庫的行為不應(yīng)該發(fā)生變化。

  • (三) 服務(wù)器

  • 【推薦】高并發(fā)服務(wù)器建議調(diào)小 TCP 協(xié)議的 time_wait 超時時間。
    說明:操作系統(tǒng)默認 240 秒后,才會關(guān)閉處于 time_wait 狀態(tài)的連接,在高并發(fā)訪問下,服務(wù)器端會因為處于 time_wait 的連接數(shù)太多,可能無法建立新的連接,所以需要在服務(wù)器上調(diào)小此等待值。
    正例:在 linux 服務(wù)器上請通過變更/etc/sysctl.conf 文件去修改該缺省值(秒):
    net.ipv4.tcp_fin_timeout = 30

  • 【推薦】調(diào)大服務(wù)器所支持的最大文件句柄數(shù)(File Descriptor,簡寫為 fd)。
    說明:主流操作系統(tǒng)的設(shè)計是將 TCP/UDP 連接采用與文件一樣的方式去管理,即一個連接對應(yīng)于一個 fd。主流的 linux 服務(wù)器默認所支持最大 fd 數(shù)量為 1024,當并發(fā)連接數(shù)很大時很容易因為 fd 不足而出現(xiàn)“open too many files”錯誤,導致新的連接無法建立。 建議將 linux 服務(wù)器所支持的最大句柄數(shù)調(diào)高數(shù)倍(與服務(wù)器的內(nèi)存數(shù)量相關(guān))。

  • 【推薦】給 JVM 設(shè)置-XX:+HeapDumpOnOutOfMemoryError 參數(shù),讓 JVM 碰到 OOM 場景時輸出
    dump 信息。
    說明:OOM 的發(fā)生是有概率的,甚至有規(guī)律地相隔數(shù)月才出現(xiàn)一例,出現(xiàn)時的現(xiàn)場信息對查錯非常有價值。

  • 【推薦】在線上生產(chǎn)環(huán)境,JVM 的 Xms 和 Xmx 設(shè)置一樣大小的內(nèi)存容量,避免在 GC 后調(diào)整堆大小帶來的壓力。

  • 【參考】服務(wù)器內(nèi)部重定向使用 forward;外部重定向地址使用 URL 拼裝工具類來生成,否則會帶來 URL 維護不一致的問題和潛在的安全風險。

  • 總結(jié)

    以上是生活随笔為你收集整理的阿里Java代码规范的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。