公司讓我編寫一套自己的【Java 編碼規範】作爲員工季度考覈標準?!參照Alibaba

目錄

一、編碼規範

(一)命名風格

(二)常量定義

(三)代碼格式

(四)OOP規範

(五)集合處理

(六)併發處理

(七)控制語句

(八)註釋規範

(九)其它

二、SVN操作規範

三、異常日誌

(一)異常處理

(二)日誌規範

四、單元測試

五、安全規範

六、Mysql數據庫

(一)建表規範

(二)索引規範

(三)SQL語句

(四)ORM映射


大家好,今年下半年,小編有個重點工作任務:幫助公司制定一個符合公司實際與發展的【Java 編碼規範】。親身參與制定之後,我本身對編碼規範的重視和理解也升到了好幾個層次。此外,還根據公司規範出了一套面試題,來考察2-4年程序員的編碼素質。

現代軟件行業的高速發展對開發者的綜合素質要求越來越高,因爲不僅是編程知識點,其它維度的知識點也會影響到軟件的最終交付質量。比如:數據庫的表結構和索引設計缺陷可能帶來軟件上的架構缺陷或性能風險;工程結構混亂導致後續維護艱難;沒有鑑權的漏洞代碼易被黑客攻擊等等。所以,統一的行爲規範,旨在碼出高效,碼出質量。

對軟件來說,適當的規範和標準絕不是消滅代碼內容的創造性、優雅性,而是限制過度個性化,以一種普遍認可的統一方式一起做事,提升協作效率,降低溝通成本。質量的提升,直接收益就是是儘可能少踩坑,杜絕了踩重複的坑,切實提升系統穩定性。

(以下內容已經刪除了個別涉及公司敏感信息的條款,請諒解)

一、編碼規範

(一)命名風格

1.【強制】代碼中的命名均不能以下劃線或美元符號開始,也不能以下劃線或美元符號結束。

反例:_name / __name / $name / name_ / name$ / name__

2.【強制】代碼中的命名嚴禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。

說明:正確的英文拼寫和語法可以讓閱讀者易於理解,避免歧義。注意,即使純拼音命名方式也要避免採用。

正例:powerpeak / taobao / youku / hangzhou 等國際通用的名稱,可視同英文。反例:DaZhePromotion [打折] / getPingfenByName() [評分] / int 某變量 = 3

3.【強制】類名使用 UpperCamelCase風格,但以下情形例外:DO / BO / DTO / VO / AO /PO / UID等。

正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion

經驗分享:

  • 類名需使用名詞組合表達其含義時,建議類名長度不超過30個字符;
  • Pojo類名儘量與數據庫中表名保持一致例如,表名USER_INFOMRATION 類名UserInformation

4.【強制】方法名、參數名、成員變量、局部變量都統一使用 lowerCamelCase風格,必須遵從

駝峯形式。

正例: localValue / getHttpMessage() / inputUserId

經驗分享

  • 變量名需使用名詞組合表達其含義時,建議變量長度不超過30個字符;
  • 當同時定義多個屬於同一個類的變量時,把類型作爲實例的後綴。如:Point  startPoint/centerPoint;
  • POJO類中的變量(字段)名儘量與表中的列名一致,如 USER_NAME應定義成員變量名  userName。

5.【強制】常量命名全部大寫,單詞間用下劃線隔開,力求語義表達完整清楚,不要嫌名字長。

正例:MAX_STOCK_COUNT反例:MAX_COUNT

6.【強制】抽象類命名使用 Abstract Base開頭異常類命名使用 Exception結尾測試類命名以它要測試的類的名稱開始,以 Test結尾。

7.【強制】類型與中括號緊挨相連來表示數組。

正例:定義整形數組 int[] arrayDemo;

反例: main 參數中,使用 String args[]來定義。

8.【強制】POJO類中布爾類型的變量,都不要加 is前綴,否則部分框架解析會引起序列化錯誤。

反例:定義爲基本數據類型 Boolean isDeleted的屬性,它的方法也是 isDeleted()RPC框架在反向解析的時候,“誤以爲”對應的屬性名稱是 deleted,導致屬性獲取不到,進而拋出異常。

9.【強制】包名統一使用小寫,點分隔符之間有且僅有一個自然語義的英語單詞。包名統一使用單數形式,但是類名如果有複數含義,類名可以使用複數形式。

正例:應用工具類包名爲 com.powerpeak.ai.util、類名爲 MessageUtils此規則參考 spring的框架結構)

經驗分享:

  • 系統邏輯複雜且功能較多是,建議使用包名區分各業務模塊,包名要有具體意義且關係清晰
  • 同一接口存在不同實現類的情況下,不同的實現類應放在不同的包內。如 com. powerpeak.pfwd.IMessurePoint 有不同的實現類如 MessurePointPHImpl ,MessurePointGDImpl 可以分別放到  com.powerpeak.pfwd.phimpl 和 com.powerpeak.pfwd.gdimpl 目錄;

10.【強制】杜絕完全不規範的縮寫,避免望文不知義。

反例:AbstractClass“縮寫”命名成 AbsClasscondition“縮寫”命名成 condi,此類隨意縮寫嚴重降低了代碼的可閱讀性。

11.【推薦】爲了達到代碼自解釋的目標,任何自定義編程元素在命名時,使用盡量完整的單詞組合來表達其意。

正例: JDK 中,表達原子更新的類名爲:AtomicReferenceFieldUpdater

反例:變量 int a 的隨意命名方式。

12.【推薦】如果模塊、接口、類、方法使用了設計模式,在命名時需體現出具體模式。

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

正例:

public class OrderFactory;

public class LoginProxy;

public class ResourceObserver;

13.【推薦】接口類中的方法和屬性不要加任何修飾符號public 也不要加,保持代碼的簡潔性,並加上有效的 Javadoc註釋。儘量不要在接口裏定義變量,如果一定要定義變量,肯定是與接口方法相關,並且是整個應用的基礎常量。

正例:接口方法簽名 void commit();

接口基礎常量 String COMPANY = "powerpeak";

反例:接口方法定義 public abstract void f();

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

14.接口和實現類的命名有兩套規則:

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

正例:CacheServiceImpl實現 CacheService接口。

2【推薦】 如果是形容能力的接口名稱,取對應的形容詞爲接口名通常是able的形式  

正例:AbstractTranslator實現 Translatable接口。

15.【參考】枚舉類名建議帶上 Enum後綴,枚舉成員名稱需要全大寫,單詞間用下劃線隔開。

說明:枚舉其實就是特殊的類,域成員均爲常量,且構造方法被默認強制是私有。

正例:枚舉名字爲 ProcessStatusEnum成員名稱:SUCCESS / UNKNOWN_REASON

16.【參考】各層命名規約:

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

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

2 獲取多個對象的方法用 list做前綴,複數形式結尾如listObjects;

3 獲取統計值的方法用 count做前綴;

4 插入的方法用 save/insert做前綴;

5 刪除的方法用 remove/delete做前綴;

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

B) 領域模型命名規約

1 數據對象:xxxDOxxx即爲數據表名;

2 數據傳輸對象:xxxDTOxxx爲業務領域相關的名稱;

3 展示對象:xxxVOxxx一般爲網頁名稱;

4) POJO DO/DTO/BO/VO的統稱,禁止命名成 xxxPOJO

(二)常量定義

1.【強制】不允許任何魔法值即未經預先定義的常量直接出現在代碼中。

反例:String key = "Id#taobao_" + tradeId;

cache.put(key, value);

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

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

3.【推薦】不要使用一個常量類維護所有常量,要按常量功能進行歸類,分開維護。

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

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

4.【推薦】常量的複用層次有五層:跨應用共享常量、應用內共享常量、子工程內共享常量、包內共享常量、類內共享常量。

1)跨應用共享常量:放置在二方庫中,通常是 client.jar中的 constant目錄下。

2)應用內共享常量:放置在一方庫中,通常是子模塊中的 constant目錄下。

反例:易懂變量也要統一定義成應用內共享常量,兩位工程師在兩個類中分別定義了表示的變量:

 A中:public static final String YES = "yes";

 B中:public static final String YES = "y";

A.YES.equals(B.YES),預期是 true,但實際返回爲 false,導致線上問題。

3)子工程內部共享常量:即在當前子工程的 constant目錄下。4)包內共享常量:即在當前包下單獨的 constant目錄下。5)類內共享常量:直接在類內部 private static final定義。

5.【推薦】如果變量值僅在一個固定範圍內變化用 enum 類型來定義。

說明:如果存在名稱之外的延伸屬性應使用 enum 類型,下面正例中的數字就是延伸信息,表示一年中的第幾個季節。

正例:

public enum SeasonEnum {

    SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);

    private int seq;

    SeasonEnum(int seq){

        this.seq = seq;

    }
}

6.【推薦】模塊的常量類存放在模塊中util包下,名稱採用【模塊名稱】+ Constants”表示方法

(三)代碼格式

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

1)左大括號前不換行。

2)左大括號後換行。

3)右大括號前換行。

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

2.【強制】左小括號和字符之間不出現空格同樣,右小括號和字符之間也不出現空格;而左大括號前需要空格。詳見第 5條下方正例提示。

反例:if (空格 a == b空格)

3.【強制】if/for/while/switch/do等保留字與括號之間都必須加空格。

4.【強制】任何二目、三目運算符的左右兩邊都需要加一個空格。

說明:運算符包括賦值運算符=、邏輯運算符&&、加減乘除符號等。

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");

        // 在右大括號後直接結束,則必須換行

    }

}

6.【強制】註釋的雙斜線與註釋內容之間有且僅有一個空格。

正例:

// 這是示例註釋,請注意在雙斜線之後有一個空格

String ygb = new String();

7.【強制】單行字符數限制不超過 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);

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

正例:下例中實參的 args1,後邊必須要有一個空格。

method(args1, args2, args3);

9.【強制】IDE text file encoding設置爲 UTF-8; IDE中文件的換行符使用Unix格式,不要使用 Windows格式。

10.【推薦】單個方法的總行數不超過 80 行。

說明:包括方法簽名、結束右大括號、方法內代碼、註釋、空行、回車及任何不可見字符的總行數不超過 80 行。

正例:代碼邏輯分清紅花和綠葉,個性和共性,綠葉邏輯單獨出來成爲額外方法,使主幹代碼更加清晰;共性邏輯抽取成爲共性方法,便於複用和維護。

11.【推薦】沒有必要增加若干空格來使某一行的字符與上一行對應位置的字符對齊。

正例:int one = 1;

long two = 2L;

float three = 3F;

StringBuffer sb = new StringBuffer();

說明:增加 sb這個變量,如果需要對齊,則給 abc都要增加幾個空格,在變量比較多的情況下,是非常累贅的事情。

12.【推薦】不同邏輯、不同語義、不同業務的代碼之間插入一個空行分隔開來以提升可讀性。

說明:任何情形,沒有必要插入多個空行進行隔開。

13.【推薦類和接口中元素按順序來佈局,具體佈局順序如下:

正例:

  • 類和接口的文檔描述;
  • 類和接口的聲明;
  • 類(非常量類)的屬性,按照privateprotected的順序;
  • 類的方法,按照 publicprotectedprivate 的順序;
  • 類中的get和set方法放在最底部。

14.【推薦避免長的布爾表達式,應換成多個更容易理解的表達式。示例如下:

正例:

bool isFinished = (elementNo < 0) || (elementNo > maxElement);

bool isRepeatedEntry = elementNo == lastElement;

if (isFinished || isRepeatedEntry) {

…

}

反例:

if ((elementNo < 0) || (elementNo > maxElement)|| elementNo ==lastElement){

…

}

15.【推薦方法的修飾關鍵字按規則順序書寫,其中訪問標示符一定要在最前面。規則如下:

<public, protected, private > static abstract synchronized unuaual final native methodName

正例:

public static double square(double a);

反例:

 static public double square(double a);

16.【推薦字符串進行頻繁拼接時,使用StringBuffer 對象的append() 方法。sql 語句使用 pl/sql 的複製模板

(四)OOP規範

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

2.【強制】所有的覆寫方法,必須加@Override註解。

說明:getObject() get0bject()的問題。一個是字母的 O,一個是數字的 0,加@Override可以準確判斷是否覆蓋成功。另外,如果在抽象類中對方法簽名進行修改,其實現類會馬上編譯報錯。

3.【強制】相同參數類型,相同業務含義,纔可以使用 Java的可變參數,避免使用 Object

說明:可變參數必須放置在參數列表的最後。提倡同學們儘量不用可變參數編程)

正例:public List<User> listUsers(String type, Long... ids) {...}

4.【強制】外部正在調用或者二方庫依賴的接口,不允許修改方法簽名,避免對接口調用方產生影響。接口過時必須加@Deprecated註解,並清晰地說明採用的新接口或者新服務是什麼。

5.【強制】不能使用過時的類或方法。

說明:java.net.URLDecoder 中的方法 decode(String encodeStr) 這個方法已經過時,應該使用雙參數 decode(String source, String encode)。接口提供方既然明確是過時接口,那麼有義務同時提供新的接口作爲調用方來說,有義務去考證過時方法的新實現是什麼。

6.【強制】Object equals方法容易拋空指針異常,應使用常量或確定有值的對象來調用equals

正例:"test".equals(object); 反例:object.equals("test");

說明:推薦使用 java.util.Objects#equalsJDK7引入的工具類)

7.【強制】所有的相同類型的包裝類對象之間值的比較,全部使用 equals方法比較。

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

8.關於基本數據類型與包裝數據類型的使用標準如下:

1)【強制】所有的 POJO類屬性必須使用包裝數據類型。

2)【強制】RPC方法的返回值和參數必須使用包裝數據類型。

3)【推薦】所有的局部變量使用基本數據類型。

說明:POJO類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何NPE問題,或者入庫檢查,都由使用者來保證。

正例:數據庫的查詢結果可能是 null,因爲自動拆箱,用基本數據類型接收有 NPE風險。

反例:比如顯示成交總額漲跌情況,即正負 x%x爲基本數據類型,調用的 RPC服務,調用不成功時,返回的是默認值,頁面顯示爲 0%,這是不合理的,應該顯示成中劃線。所以包裝 數據類型的 null值,能夠表示額外的信息,如:遠程調用失敗,異常退出。

9.【強制】定義 DO/DTO/VO POJO類時,不要設定任何屬性默認值

反例:POJO類的 gmtCreate默認值爲 new Date()但是這個屬性在數據提取時並沒有置入具體值,在更新其它字段時又附帶更新了此字段,導致創建時間被修改成當前時間。

10.【強制】序列化類新增屬性時,請不要修改 serialVersionUID字段,避免反序列失敗如果完全不兼容升級,避免反序列化混亂,那麼請修改 serialVersionUID值。

說明:注意 serialVersionUID不一致會拋出序列化運行時異常。

11.【強制】構造方法裏面禁止加入任何業務邏輯,如果有初始化邏輯,請放在 init方法中。

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

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

13.【強制】禁止在 POJO類中,同時存在對應屬性 xxx isXxx() getXxx()方法。

說明:框架在調用屬性 xxx 的提取方法時,並不能確定哪個方法一定是被優先調用到。

14.【推薦】使用索引訪問用 String split方法得到的數組時,需做最後一個分隔符後有無內容的檢查,否則會有拋 IndexOutOfBoundsException的風險。

說明:

String str = "a,b,c,,"; String[] ary = str.split(",");

// 預期大於 3,結果是 3

System.out.println(ary.length);

15.【推薦】當一個類有多個構造方法,或者多個同名方法,這些方法應該按順序放置在一起,便於閱讀,此條規則優先於第 16條規則。

16.【推薦】 類內方法定義的順序依次是:公有方法或保護方法 > 私有方法 >getter/setter方法。

說明:公有方法是類的調用者和維護者最關心的方法,首屏展示最好保護方法雖然只是子類 關心,也可能是模板設計模式下的核心方法而私有方法外部一般不需要特別關心,是一個黑盒實現因爲承載的信息價值較低,所有 Service DAO getter/setter方法放在類體最後。

17.【推薦】setter方法中,參數名稱與類成員變量名稱一致,this.成員名 = 參數名。在getter/setter方法中,不要增加業務邏輯,增加排查問題的難度。

反例:

public Integer getData() {

    if (condition) {

        return this.data + 100;

    } else {

        return this.data - 100;

    }
}

18.【推薦】循環體內,字符串的連接方式,使用 StringBuilder append方法進行擴展。

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

反例:

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

19.【推薦】final可以聲明類、成員變量、方法、以及本地變量,下列情況使用 final關鍵字:

1)不允許被繼承的類,如:String類;

2)不允許修改引用的域對象;

3)不允許被重寫的方法,如:POJO類的 setter方法

4)不允許運行過程中重新賦值的局部變量;

5)避免上下文重複使用一個變量,使用 final描述可以強制重新定義一個變量,方便更好地進行重構。

20.【推薦】慎用 Object clone方法來拷貝對象。

說明:對象的 clone方法默認是淺拷貝,若想實現深拷貝需要重寫 clone方法實現域對象的深度遍歷式拷貝。

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

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成員方法或成員變量,刪除一下,不得手心冒點汗嗎?變量像自己的小孩,儘量在自己的視線內,變量作用域太大,無限制的到處跑,那麼你會擔心的。

(五)集合處理

1.【強制】關於 hashCode和 equals的處理,遵循如下規則:

1)只要重寫 equals,就必須重寫 hashCode

2)因爲 Set存儲的是不重複的對象,依據 hashCode equals進行判斷,所以 Set存儲的對象必須重寫這兩個方法;

3)如果自定義對象作爲 Map的鍵,那麼必須重寫 hashCode equals

說明:String重寫了 hashCode equals方法,所以我們可以非常愉快地使用 String對象作爲 key來使用。

2.【強制】ArrayListsubList結果不可強轉成ArrayList,否則會拋出ClassCastException異常,即 java.util.RandomAccessSubList cannot be cast to java.util.ArrayList

說明:subList 返回的是 ArrayList 的內部類 SubList,並不是 ArrayList而是 ArrayList的一個視圖,對於 SubList子列表的所有操作最終會反映到原列表上。

3.【強制】在 subList場景中,高度注意對原集合元素的增加或刪除,均會導致子列表的遍歷、增加、刪除產生 ConcurrentModificationException 異常。

4.【強制】使用集合轉數組的方法,必須使用集合的 toArray(T[] array),傳入的是類型完全一樣的數組,大小就是 list.size()

說明:使用 toArray帶參方法,入參分配的數組空間不夠大時,toArray方法內部將重新分配內存空間,並返回新數組地址如果數組元素個數大於實際所需,下標爲[ list.size() ]的數組元素將被置爲 null,其它數組元素保持原值,因此最好將方法入參數組大小定義與集合元素個數一致。

正例:

    List<String> list = new ArrayList<String>(2);list.add("guan"); list.add("bao");
    String[] array = new String[list.size()];array = list.toArray(array);

反例:直接使用 toArray無參方法存在問題,此方法返回值只能是 Object[]類,若強轉其它類型數組將出現 ClassCastException錯誤。

5.【強制】使用工具類 Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方法,它的 add/remove/clear方法會拋出 UnsupportedOperationException異常。

說明:asList的返回對象是一個 Arrays內部類,並沒有實現集合的修改方法。Arrays.asList 體現的是適配器模式,只是轉換接口,後臺的數據仍是數組。

String[] str = new String[] { "you", "wu" };
List list = Arrays.asList(str);

第一種情況:list.add("yangguanbao"); 運行時異常。

第二種情況:str[0] = "gujin"; 那麼 list.get(0)也會隨之修改。

6.【強制】泛型通配符<? extends T>來接收返回的數據,此寫法的泛型集合不能使用 add方法,而<? super T>不能使用 get方法,作爲接口調用賦值時易出錯。

說明:擴展說一下 PECS(Producer Extends Consumer Super)原則:

第一、頻繁往外讀取內容的,適合用<? extends T>

第二、經常往裏插入的,適合用<? super T>

7.【強制】不要在 foreach循環裏進行元素的 remove/add操作。remove元素請使用 Iterator方式,如果併發操作,需要對 Iterator對象加鎖。

正例:

List<String> list = new ArrayList<>();list.add("1"); list.add("2");
Iterator<String> iterator = list.iterator();while (iterator.hasNext()) { 
    String item = iterator.next(); if (刪除元素的條件) {
        iterator.remove();
    }
}

反例

for (String item : list) {
    if ("1".equals(item)) {
        list.remove(item);
    }
}

說明:以上代碼的執行結果肯定會出乎大家的意料,那麼試一下把“1”換成“2”,會是同樣的結果嗎?

8.【強制】  JDK7版本及以上,Comparator實現類要滿足如下三個條件,不然 Arrays.sortCollections.sort會報 IllegalArgumentException異常。

說明:三個條件如下

1xy的比較結果和 yx的比較結果相反;

2x>yy>z,則 x>z

3x=y,則 xz比較結果和 yz比較結果相同。

反例:下例中沒有處理相等的情況,實際使用中可能會出現異常:

new Comparator<Student>() {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getId() > o2.getId() ? 1 : -1;
    }
};

9.【推薦】集合泛型定義時,在 JDK7及以上,使用 diamond語法或全省略。

說明:菱形泛型,即 diamond,直接使用<>來指代前邊已經指定的類型。

正例:

// <> diamond方式

HashMap<String, String> userCache = new HashMap<>(16);

// 全省略方式

ArrayList<User> users = new ArrayList(10);

10.【推薦】集合初始化時,指定集合初始值大小。

說明:HashMap使用 HashMap(int initialCapacity) 初始化。

正例:initialCapacity= (需要存儲的元素個數 / 負載因子)+1。注意負載因子(即 loaderfactor)默認爲 0.75如果暫時無法確定初始值大小,請設置爲 16(即默認值)。

反例:HashMap需要放置 1024個元素,由於沒有設置容量初始大小,隨着元素不斷增加,容 量 7 次被迫擴大,resize需要重建 hash表,嚴重影響性能。

11.【推薦】使用 entrySet遍歷 Map類集合 KV,而不是 keySet方式進行遍歷。

說明:keySet其實是遍歷了 2次,一次是轉爲 Iterator對象,另一次是從 hashMap中取出key所對應的 value。而 entrySet只是遍歷了一次就把 key value都放到了 entry中,效率更高。如果是 JDK8,使用 Map.foreach方法。

正例:values()返回的是 V值集合,是一個 list集合對象keySet()返回的是 K值集合,是一個 Set集合對象entrySet()返回的是 K-V值組合集合。

12.【推薦】高度注意 Map類集合 K/V能不能存儲 null值的情況,如下表格:

集合類

Key

Value

Super

說明

Hashtable

不允許爲 null

不允許爲 null

Dictionary

線程安全

ConcurrentHashMap

不允許爲 null

不允許爲 null

AbstractMap

鎖分段技術(JDK8:CAS

TreeMap

不允許爲 null

允許爲 null

AbstractMap

線程不安全

HashMap

允許爲 null

允許爲 null

AbstractMap

線程不安全

反例: 由於 HashMap的干擾,很多人認爲 ConcurrentHashMap是可以置入 null值,而事實上,存儲 null值時會拋出 NPE異常。

13.【參考】合理利用好集合的有序性(sort)和穩定性(order),避免集合的無序性(unsort)和不穩定性(unorder)帶來的負面影響。說明:有序性是指遍歷的結果是按某種比較規則依次排列的。穩定性指集合每次遍歷的元素次序是一定的。如:ArrayList order/unsortHashMap unorder/unsortTreeSetorder/sort

14.【參考】利用 Set元素唯一的特性,可以快速對一個集合進行去重操作,避免使用 Listcontains方法進行遍歷、對比、去重操作。

(六)併發處理

1.【強制】獲取單例對象需要保證線程安全,其中的方法也要保證線程安全。

說明:資源驅動類、工具類、單例工廠類都需要注意。

2.【強制】創建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。

正例:

public class TimerTaskThread extends Thread {

    public TimerTaskThread() {

        super.setName("TimerTaskThread"); 
        ...

    }
}

3.【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。

說明:使用線程池的好處是減少在創建和銷燬線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或過度切換的問題。

4.【強制】線程池不允許使用 Executors去創建,而是通過 ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。

說明:Executors返回的線程池對象的弊端如下:

1FixedThreadPool SingleThreadPool:

允許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM

2CachedThreadPool ScheduledThreadPool:

允許的創建線程數量爲 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM

5.【強制】SimpleDateFormat 是線程不安全的類,一般不要定義爲 static變量,如果定義爲static,必須加鎖,或者使用 DateUtils工具類。

正例:注意線程安全,使用 DateUtils。亦推薦如下處理:

private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {

    @Override
    protected DateFormat initialValue() {

        return new SimpleDateFormat("yyyy-MM-dd");

    }

};

說明:如果是 JDK8的應用,可以使用 Instant代替 DateLocalDateTime代替 CalendarDateTimeFormatter代替 SimpleDateFormat,官方給出的解釋:simple beautiful strongimmutable thread-safe

6.【強制】高併發時,同步調用應該去考量鎖的性能損耗。能用無鎖數據結構,就不要用鎖能鎖區塊,就不要鎖整個方法體能用對象鎖,就不要用類鎖。

說明:儘可能使加鎖的代碼塊工作量儘可能的小,避免在鎖代碼塊中調用 RPC 方法。

7.【強制】對多個資源、數據庫表、對象同時加鎖時,需要保持一致的加鎖順序,否則可能會造成死鎖。

說明:線程一需要對錶 ABC依次全部加鎖後纔可以進行更新操作,那麼線程二的加鎖順序也必須是 ABC,否則可能出現死鎖。

8.【強制】併發修改同一記錄時,避免更新丟失,需要加鎖。要麼在應用層加鎖,要麼在緩存加鎖,要麼在數據庫層使用樂觀鎖,使用 version作爲更新依據。

說明:如果每次訪問衝突概率小於 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數不得小於 3次。

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

10.【推薦】使用 CountDownLatch進行異步轉同步操作,每個線程退出前必須調用 countDown方法,線程執行代碼注意 catch異常,確保 countDown方法被執行到,避免主線程無法執行至 await方法,直到超時才返回結果。

說明:注意,子線程拋出異常堆棧,不能在主線程 try-catch到。

11.【推薦】避免 Random實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一seed 導致的性能下降。

說明:Random實例包括 java.util.Random 的實例或者 Math.random()的方式。 正例: JDK7之後,可以直接使用 API ThreadLocalRandom,而在 JDK7之前,需要編碼保 證每個線程持有一個實例。

12.【推薦】在併發場景下,通過雙重檢查鎖double-checked locking實現延遲初始化的優化問題隱患(可參考 The "Double-Checked Locking is Broken" Declaration),推薦解決方案中較爲簡單一種適用於 JDK5及以上版本,將目標屬性聲明爲 volatile

反例:

class LazyInitDemo {

    private Helper helper = null; public Helper getHelper() {
        if (helper == null) synchronized(this) {
            if (helper == null) helper = new Helper();
        }
        return helper; 
    }
     // other methods and fields...
}

13.【參考】volatile解決多線程內存不可見問題。對於一寫多讀,是可以解決變量同步問題,但是如果多寫,同樣無法解決線程安全問題。

如果是 count++操作,使用如下類實現:AtomicInteger count = new AtomicInteger(); count.addAndGet(1);

如果是 JDK8,推 薦使用 LongAdder對象,比 AtomicLong性能更好減少樂觀鎖的重試次數

14.【參考】 HashMap在容量不夠進行 resize時由於高併發可能出現死鏈,導致 CPU飆升,在開發過程中可以使用其它數據結構或加鎖來規避此風險。

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

(七)控制語句

1.【強制】在一個 switch塊內,每個 case要麼通過 break/return等來終止,要麼註釋說明程序將繼續執行到哪一個 case爲止在一個 switch塊內,都必須包含一個 default語句並且放在最後,即使空代碼。

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

3.【強制】在高併發場景中,避免使用等於判斷作爲中斷或退出的條件。

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

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

4.【推薦】表達異常的分支時,少用 if-else方式,這種方式可以改寫成:

if (condition) {
    ...
    return obj;
}
// 接着寫 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 powerpeak Java Coding Guidelines.”);
    return;
}

5.【推薦】除常用方法(如 getXxx/isXxx)等外,不要在條件判斷中執行其它複雜的語句,將複雜邏輯判斷的結果賦值給一個有意義的布爾變量名,以提高可讀性。

說明:很多 if 語句內的邏輯相當複雜,閱讀者需要分析條件表達式的最終結果,才能明確什麼樣的條件執行什麼樣的語句,那麼,如果閱讀者分析邏輯表達式錯誤呢?

正例:

// 僞代碼如下

final boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed) {

 ...}

反例:

if ((file.open(fileName, "w") != null) && (...) || (...)) {

 ...}

6.【推薦】循環體中的語句要考量性能,以下操作儘量移至循環體外處理,如定義對象、變量、獲取數據庫連接,進行不必要的 try-catch操作這個 try-catch是否可以移至循環體外

7.【推薦】避免採用取反邏輯運算符。

說明:取反邏輯不利於快速理解,並且取反邏輯寫法必然存在對應的正向邏輯寫法。

正例:使用 if (x < 628) 來表達 x 小於 628

反例:使用 if (!(x >= 628)) 來表達 x 小於 628

8.【推薦】接口入參保護,這種場景常見的是用作批量操作的接口。

9.【參考】下列情形,需要進行參數校驗:

1)調用頻次低的方法;

2)執行時間開銷很大的方法。此情形中,參數校驗時間幾乎可以忽略不計,但如果因爲參數錯誤導致中間執行回退,或者錯誤,那得不償失;

3)需要極高穩定性和可用性的方法;

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

 5)敏感權限入口。

10.【參考】下列情形,不需要進行參數校驗:

1)極有可能被循環調用的方法。但在方法說明裏必須註明外部參數檢查要求;

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

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

 

(八)註釋規範

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

說明: IDE編輯窗口中,Javadoc方式會提示相關注釋,生成 Javadoc可以正確輸出相應註釋 IDE中,工程調用方法時,不進入方法即可懸浮提示方法、參數、返回值的意義,提高閱讀效率。

2.【強制】所有的抽象方法(包括接口中的方法必須要用 Javadoc註釋、除了返回值、參數、異常說明外,還必須指出該方法做什麼事情,實現什麼功能。

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

3.【強制】所有的類都必須添加創建者和創建日期。

4.【強制】方法內部單行註釋,在被註釋語句上方另起一行,使用//註釋。方法內部多行註釋使用/* */註釋,注意與代碼對齊。

5.【強制】所有的枚舉類型字段必須要有註釋,說明每個數據項的用途。

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

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

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

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

8.【參考】謹慎註釋掉代碼。在上方詳細說明,而不是簡單地註釋掉。如果無用,則刪除。

說明:代碼被註釋掉有兩種可能性:1後續會恢復此段代碼邏輯。2永久不用。前者如果沒有備註信息,難以知曉註釋動機。後者建議直接刪掉代碼倉庫保存了歷史代碼

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

10.【參考】好的命名、代碼結構是自解釋的,註釋力求精簡準確、表達到位。避免出現註釋的一個極端:過多過濫的註釋,代碼的邏輯一旦修改,修改註釋是相當大的負擔。

反例:

// put elephant into fridgeput(elephant, fridge);

方法名 put,加上兩個有意義的變量名 elephant fridge,已經說明了這是在幹什麼,語義清晰的代碼不需要額外的註釋。

11.【參考】特殊註釋標記,請註明標記人與標記時間。注意及時處理這些標記,通過標記掃描,經常清理此類標記。線上故障有時候就是來源於這些標記處的代碼。

1)待辦事宜TODO: 標記人,標記時間,[預計處理時間]表示需要實現,但目前還未實現的功能。這實際上是一個 Javadoc的標籤,目前的 Javadoc還沒有實現,但已經被廣泛使用。只能應用於類,接口和方法因爲它是一個 Javadoc標籤);

2)錯誤,不能工作FIXME:標記人,標記時間,[預計處理時間])在註釋中用 FIXME標記某代碼是錯誤的,而且不能工作,需要及時糾正的情況。

(九)其它

1.【強制】在使用正則表達式時,利用好其預編譯功能,可以有效加快正則匹配速度。

說明:不要在方法體內定義:Pattern pattern = Pattern.compile(規則);

2.【強制】velocity調用 POJO類的屬性時,建議直接使用屬性名取值即可,模板引擎會自動按規範調用 POJO getXxx(),如果是 boolean基本數據類型變量boolean命名不需要加 is前綴,會自動調用 isXxx()方法。

說明:注意如果是 Boolean包裝類對象,優先調用 getXxx()的方法。

3.【強制】後臺輸送給頁面的變量必須加$!{var}——中間的感嘆號。

說明:如果 var等於 null或者不存在,那麼${var}會直接顯示在頁面上。

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

5.【強制】獲取當前毫秒數 System.currentTimeMillis(); 而不是 new Date().getTime();

說明:如果想獲取更加精確的納秒級時間值,使用 System.nanoTime()的方式。在 JDK8中,針對統計時間等場景,推薦使用 Instant類。

6.【推薦】不要在視圖模板中加入任何複雜的邏輯。

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

7.【推薦】任何數據結構的構造或初始化,都應指定大小,避免數據結構無限增長喫光內存。

8.【推薦】及時清理不再使用的代碼段或配置信息。

說明:對於垃圾代碼或過時配置,堅決清理乾淨,避免程序過度臃腫,代碼冗餘。

正例:對於暫時被註釋掉,後續可能恢復使用的代碼片斷,在註釋代碼上方,統一規定使用三個斜槓(///)來說明註釋掉代碼的理由。

二、SVN操作規範

1.【強制】提交代碼中不能存在空循環體和調試語句;

2.【強制】用SVN提交代碼時寫清楚備註,備註內容同文件頭部的修改信息;

3.【強制】提交粒度是測試之後的一個完整功能;

4.【強制】提交代碼前確保使用SoTower自帶的代碼格式化功能進行一次Format。

三、異常日誌

(一)異常處理

1.【強制】Java 類庫中定義的可以通過預檢查方式規避的 RuntimeException異常不應該通過catch 的方式來處理,比如:NullPointerExceptionIndexOutOfBoundsException等等。

說明:無法通過預檢查的異常除外,比如,在解析字符串形式的數字時,不得不通過 catchNumberFormatException來實現。

正例:if (obj != null) {...}

反例:try { obj.method(); } catch (NullPointerException e) {…}

2.【強制】異常不要用來做流程控制,條件控制。

說明:異常設計的初衷是解決程序運行中的各種意外情況,且異常的處理效率比條件判斷方式要低很多。

3.【強制】catch時請分清穩定代碼和非穩定代碼,穩定代碼指的是無論如何不會出錯的代碼。對於非穩定代碼的 catch儘可能進行區分異常類型,再做對應的異常處理。

說明:對大段代碼進行 try-catch,使程序無法根據不同的異常做出正確的應激反應,也不利於定位問題,這是一種不負責任的表現。

正例:用戶註冊的場景中,如果用戶輸入非法字符,或用戶名稱已存在,或用戶輸入密碼過於簡單,在程序上作出分門別類的判斷,並提示給用戶。

4.【強制】捕獲異常是爲了處理它,不要捕獲了卻什麼都不處理而拋棄之,如果不想處理它,請將該異常拋給它的調用者。最外層的業務使用者,必須處理異常,將其轉化爲用戶可以理解的內容。

5.【強制】 try塊放到了事務代碼中,catch異常後,如果需要回滾事務,一定要注意手動回滾事務。

6.【強制】finally塊必須對資源對象、流對象進行關閉,有異常也要做 try-catch

說明:如果 JDK7及以上,可以使用 try-with-resources方式。

7.【強制】不要在 finally塊中使用 return

說明:finally塊中的 return返回後方法結束執行,不會再執行 try塊中的 return語句。

8.【強制】捕獲異常與拋異常,必須是完全匹配,或者捕獲異常是拋異常的父類。

說明:如果預期對方拋的是繡球,實際接到的是鉛球,就會產生意外情況。

9.【推薦】方法的返回值可以爲 null,不強制返回空集合,或者空對象等,必須添加註釋充分說明什麼情況下會返回 null值。

說明:本手冊明確防止 NPE是調用者的責任。即使被調用方法返回空集合或者空對象,對調用者來說,也並非高枕無憂,必須考慮到遠程調用失敗、序列化失敗、運行時異常等場景返回null的情況。

10.【推薦】防止 NPE,是程序員的基本修養,注意 NPE產生的場景:

1)返回類型爲基本數據類型,return包裝數據類型的對象時,自動拆箱有可能產生 NPE

反例:public int f() { return Integer對象} 如果爲 null,自動解箱拋 NPE

2)數據庫的查詢結果可能爲 null;

3)集合裏的元素即使 isNotEmpty,取出的數據元素也可能爲 null

4)遠程調用返回對象時,一律要求進行空指針判斷,防止 NPE

5)對於 Session中獲取的數據,建議 NPE檢查,避免空指針;

6)級聯調用 obj.getA().getB().getC()一連串調用,易產生 NPE

正例:使用 JDK8 Optional類來防止 NPE問題。

11.【推薦】定義時區分 unchecked/checked 異常,避免直接拋出 new RuntimeException(),更不允許拋出 Exception或者 Throwable,應使用有業務含義的自定義異常。推薦業界已定義過的自定義異常,如:DAOException / ServiceException等。

12.【參考】對於公司外的 http/api開放接口必須使用錯誤碼而應用內部推薦異常拋出;跨應用間 RPC調用優先考慮使用 Result方式,封裝 isSuccess()方法、錯誤碼錯誤簡短信息

說明:關於 RPC方法返回方式使用 Result方式的理由:

1使用拋異常返回方式,調用方如果沒有捕獲到就會產生運行時錯誤;

2如果不加棧信息,只是 new自定義異常,加入自己的理解的 error message,對於調用端解決問題的幫助不會太多。

如果加了棧信息,在頻繁調用出錯的情況下,數據序列化和傳輸的性能損耗也是問題。

13.【參考】避免出現重複的代碼Dont Repeat Yourself,即 DRY原則。

說明:隨意複製和粘貼代碼,必然會導致代碼的重複,在以後需要修改時,需要修改所有的副本,容易遺漏。必要時抽取共性方法,或者抽象公共類,甚至是組件化。

正例:一個類中有多個 public方法,都需要進行數行相同的參數校驗操作,這個時候請抽取:private boolean checkParam(DTO dto) {...}

(二)日誌規範

1.【強制】應用中不可直接使用日誌系統(Log4j、Logback)中的 API,而應依賴使用日誌框架SLF4J中的 API,使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式統一。

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

private static final Logger logger = LoggerFactory.getLogger(Abc.class);

2.【強制】日誌文件至少保存 15天,因爲有些異常具備以爲頻次發生的特點。

3.【強制】應用中的擴展日誌如打點、臨時監控、訪問日誌等命名方式:appName_logType_logName.loglogType:日誌類型,如 stats/monitor/accesslogName:日誌描述。這種命名的好處:通過文件名就可知道日誌文件屬於什麼應用,什麼類型,什麼目的,也有利於歸類查找。

正例:mppserver應用中單獨監控時區轉換異常,如:mppserver_monitor_timeZoneConvert.log

說明:推薦對日誌進行分類,如將錯誤日誌和業務日誌分開存放,便於開發人員查看,也便於通過日誌對系統進行及時監控。

4.【強制】對 trace/debug/info級別的日誌輸出,必須使用條件輸出形式或者使用佔位符的方式。

說明:logger.debug("Processing trade with id:"+id +"and symbol:"+symbol);如果日誌級別是 warn,上述日誌不會打印,但是會執行字符串拼接操作,如果 symbol是對象,會執行 toString()方法,浪費了系統資源,執行了上述操作,最終日誌卻沒有打印。

正例:條件建設採用如下方式

if (logger.isDebugEnabled()) {

    logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);

}

正例:佔位符)logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);

5.【強制】避免重複打印日誌,浪費磁盤空間,務必在 log4j.xml中設置 additivity=false

正例:<logger name="com.taobao.dubbo.config" additivity="false">

6.【強制】異常信息應該包括兩類信息:案發現場信息和異常堆棧信息。如果不處理,那麼通過關鍵字 throws往上拋出。

正例:logger.error(各類參數或者對象 toString() + "_" + e.getMessage(), e);

7.【推薦】謹慎地記錄日誌。生產環境禁止輸出 debug日誌有選擇地輸出 info日誌如果使 warn來記錄剛上線時的業務行爲信息,一定要注意日誌輸出量的問題,避免把服務器磁盤撐爆,並記得及時刪除這些觀察日誌。

說明:大量地輸出無效日誌,不利於系統性能提升,也不利於快速定位錯誤點。記錄日誌時請思考:這些日誌真的有人看嗎?看到這條日誌你能做什麼?能不能給問題排查帶來好處?

8.【推薦】可以使用 warn 日誌級別來記錄用戶輸入參數錯誤的情況,避免用戶投訴時,無所適從。如非必要,請不要在此場景打出 error 級別,避免頻繁報警。

說明:注意日誌輸出的級別,error 級別只記錄系統邏輯出錯、異常或者重要的錯誤信息。

9.【推薦】儘量用英文來描述日誌錯誤信息,如果日誌中的錯誤信息用英文描述不清楚的話使用中文描述即可,否則容易產生歧義。國際化團隊或海外部署的服務器由於字符集問題,【強制】使用全英文來註釋和描述日誌錯誤信息。

四、單元測試

1.【強制】好的單元測試必須遵守 AIR原則。

說明:單元測試在線上運行時,感覺像空氣AIR一樣並不存在,但在測試質量的保障上,卻是非常關鍵的。好的單元測試宏觀上來說,具有自動化、獨立性、可重複執行的特點。

l AAutomatic(自動化)

l IIndependent(獨立性)

l RRepeatable(可重複)

2.【強制】單元測試應該是全自動執行的,並且非交互式的。測試用例通常是被定期執行的,執行過程必須完全自動化纔有意義。輸出結果需要人工檢查的測試不是一個好的單元測試。單元測試中不準使用 System.out來進行人肉驗證,必須使用 assert來驗證。

3.【強制】保持單元測試的獨立性。爲了保證單元測試穩定可靠且便於維護,單元測試用例之間決不能互相調用,也不能依賴執行的先後次序。

反例:method2需要依賴 method1的執行,將執行結果作爲 method2的輸入。

4.【強制】單元測試是可以重複執行的,不能受到外界環境的影響。

說明:單元測試通常會被放到持續集成中,每次有代碼 check in時單元測試都會被執行。如果單測對外部環境(網絡、服務、中間件等)有依賴,容易導致持續集成機制的不可用。

正例:爲了不受外界環境影響,要求設計代碼時就把 SUT的依賴改成注入,在測試時用 spring這樣的 DI框架注入一個本地(內存)實現或者 Mock實現。

5.【強制】對於單元測試,要保證測試粒度足夠小,有助於精確定位問題。單測粒度至多是類級別,一般是方法級別。

說明:只有測試粒度小才能在出錯時儘快定位到出錯位置。單測不負責檢查跨類或者跨系統的交互邏輯,那是集成測試的領域。

6.【強制】核心業務、核心應用、核心模塊的增量代碼確保單元測試通過。

說明:新增代碼及時補充單元測試,如果新增代碼影響了原有單元測試,請及時修正。

7.【強制】單元測試代碼必須寫在如下工程目錄:src/test/java,不允許寫在業務代碼目錄下。

說明:源碼構建時會跳過此目錄,而單元測試框架默認是掃描此目錄。

8.【推薦】單元測試的基本目標:語句覆蓋率達到 70%;核心模塊的語句覆蓋率和分支覆蓋率都要達到 100%

說明:在工程規約的應用分層中提到的 DAO層,Manager層,可重用度高的 Service,都應該進行單元測試。

9.【推薦】編寫單元測試代碼遵守 BCDE原則,以保證被測試模塊的交付質量。

l BBorder,邊界值測試,包括循環邊界、特殊取值、特殊時間點、數據順序等。

l CCorrect,正確的輸入,並得到預期的結果。

l DDesign,與設計文檔相結合,來編寫單元測試。

l EError,強制錯誤信息輸入(如:非法數據、異常流程、非業務允許輸入等),並得到預期的結果。

10.【推薦】對於數據庫相關的查詢,更新,刪除等操作,不能假設數據庫裏的數據是存在的,或者直接操作數據庫把數據插入進去,請使用程序插入或者導入數據的方式來準備數據。

反例:刪除某一行數據的單元測試,在數據庫中,先直接手動增加一行作爲刪除目標,但是這一行新增數據並不符合業務插入規則,導致測試結果異常。

11.【推薦】和數據庫相關的單元測試,可以設定自動回滾機制,不給數據庫造成髒數據。或者對單元測試產生的數據有明確的前後綴標識。

正例: RDC內部單元測試中,使用 RDC_UNIT_TEST_的前綴標識數據。

12.【推薦】對於不可測的代碼建議做必要的重構,使代碼變得可測,避免爲了達到測試要求而書寫不規範測試代碼。

13.【推薦】在設計評審階段,開發人員需要和測試人員一起確定單元測試範圍,單元測試最好覆蓋所有測試用例。

14.【推薦】單元測試作爲一種質量保障手段,不建議項目發佈後補充單元測試用例,建議在項目提測前完成單元測試。

15.【參考】爲了更方便地進行單元測試,業務代碼應避免以下情況:

l構造方法中做的事情過多。

l存在過多的全局變量和靜態方法。

l存在過多的外部依賴。

l存在過多的條件語句。

說明:多層條件語句建議使用衛語句、策略模式、狀態模式等方式重構。

16.【參考】不要對單元測試存在如下誤解:

l那是測試同學乾的事情。本文是開發手冊,凡是本文內容都是與開發同學強相關的。

l單元測試代碼是多餘的。系統的整體功能與各單元部件的測試正常與否是強相關的。

l單元測試代碼不需要維護。一年半載後,那麼單元測試幾乎處於廢棄狀態。

l單元測試與線上故障沒有辯證關係。好的單元測試能夠最大限度地規避線上故障。

 

五、安全規範

1.【強制】隸屬於用戶個人的頁面或者功能必須進行權限控制校驗。

說明:防止沒有做水平權限校驗就可隨意訪問、修改、刪除別人的數據,比如查看他人的私信內容、修改他人的訂單。

2.【強制】用戶敏感數據禁止直接展示,必須對展示數據進行脫敏。

說明:中國大陸個人手機號碼顯示爲:158****9119,隱藏中間 4位,防止隱私泄露。

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

4.【強制】用戶請求傳入的任何參數必須做有效性驗證。

說明:忽略參數校驗可能導致:

lpage size過大導致內存溢出

l惡意 order by導致數據庫慢查詢

l任意重定向

lSQL注入

l反序列化注入

l正則輸入源串拒絕服務 ReDoS

說明:Java 代碼用正則來驗證客戶端的輸入,有些正則寫法驗證普通用戶輸入沒有問題,但是如果攻擊人員使用的是特殊構造的字符串來驗證,有可能導致死循環的結果。

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

6.【強制】表單、AJAX提交必須執行 CSRF安全驗證。

說明:CSRF(Cross-site request forgery)跨站請求僞造是一類常見編程漏洞。對於存在CSRF漏洞的應用/網站,攻擊者可以事先構造好 URL,只要受害者用戶一訪問,後臺便在用戶 不知情的情況下對數據庫中用戶參數進行相應修改。

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

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

8.【強制組裝sql語句的時候爲防止sql注入應避免直接拼接字符串,採用預編譯的方式先把sql語句傳入,而後傳入參數。例:

正例:

Object[] params={"f",”小劉”};

List list=hibernateDao.executeSqlQuery("SELECT SEX,NAME FROM TEST WHERE  SEX=?  AND NAME = ? ", params);

//Hql用法:
List list=hibernateDao.findAll("select sex,name from TEST t WHERE  t.sex=?  and t.name = ? ", params);

9.【強制】發貼、評論、發送即時消息等用戶生成內容的場景必須實現防刷、文本內容違禁詞過濾等風控策略。

10.【強制在項目中需要自定義新的xml類型時,需要指定xml文檔類型文檔類型定義分爲內部聲明DTD和外部引入DTD,當使用外部引用時需要判斷路徑合法性,因爲外部引入會導致Xml外部實體注入:

反例:

  <?xml version="1.0" encoding="utf-8"?>

  <!DOCTYPE xdsec [

  <!ELEMENT methodname ANY >

  <!ENTITY xxe(實體引用名) SYSTEM "file:///etc/passwd"(實體內容) >]>

  <methodcall>

  <methodname>&xxe;</methodname>

  </methodcall>

這種寫法則調用了本地計算機的文件/etc/passwd,如果file路徑爲惡意地址,XML內容會被解析,文件內容便通過&xxe被存放在了methodname元素中,造成了敏感信息的泄露。

正例

  1. 定義Xml文件白名單,驗證輸入的路徑是否在白名單中,如不在白名單中則返回空,使用白名單可以防止
  2. 過濾關鍵字:<\!DOCTYPE和<\!ENTITY,或者SYSTEM和PUBLIC。
  3. 僅使用內部聲明DTD避免使用外部引用DTD

 

11.【強制當頁面輸入框且數據需要與後臺交互時,要對用戶輸入的信息前端進行校驗前端校驗可避免存儲型xss攻擊,如下所示:

正例:

(1)對用戶的輸入進行合理驗證(如年齡只能是數字)。

(2)根據數據將要置於HTML上下文中的不同位置(HTML標籤、HTML屬性、

JavaScript腳本、CSS、URL),對所有不可信數據進行恰當的輸出編碼。

(3)設置HttpOnly屬性,避免攻擊者利用跨站腳本漏洞進行Cookie劫持攻擊。在JavaEE中,給Cookie添加HttpOnly的代碼如下:

...

response.setHeader(“Set-Cookie”,”cookiename=cookievalue;path= /;

Domain=dom ain vaule;Max-age= seconds;HttpOnly”);

...

例如:採用OWASPESAPI對數據輸出HTML上下文中不同位置,編碼方法如下:

//HTMLencode

ESAPI.encoder().encodeForHTML("inputData");

//HTML attribute encode

ESAPI.encoder().encodeForHTMLAttribute("inputData ");

//JavaScriptencode

ESAPI.encoder().encodeForJavaScript("inputData");

//CSS encode

ESAPI.encoder().encodeForCSS(""inputData");

//URL encode

ESAPI.encoder().encodeForURL("inputData");

12.【強制驗證請求來源合法性,防止當cookie劫持時,利用網站對用戶標識信任

跨站請求僞造(csrf

說明:

攻擊者盜用了你的身份,以你的名義發送惡意請求,對服務器來說這個請求是完全合法的,但是卻完成了攻擊者所期望的一個操作,比如以你的名義發送郵件、發消息,盜取你的賬號,添加系統管理員,甚至於購買商品、虛擬貨幣轉賬等。 如下:其中Web A爲存在CSRF漏洞的網站,Web B爲攻擊者構建的惡意網站,User C爲Web A網站的合法用戶。

正例:referer頭過濾

正例:

Token認證,對每一次請求的a標籤或form生產一個token,token信息放在請求頭中

13.【強制如數組聲明爲final的public static,需保證數組對象本身只分配一次,且不可以改變數組中存儲的值

14.【強制】JavaEE程序:session中存儲非序列化對象,在session中存儲的對象所屬類以及引用對象所屬類,都需要實現Serializable接口,對封裝到jar包裏的非序列化實體類,可以考慮轉化爲自己定義的類,然後再存入到session中

15.【強制硬編碼文件分隔符,在使用路徑分隔符時,要用File.separator,不能直接使用“/”或“\”。

16.【強制比較locale相關的數據未指定適當的locale,在調用與國際化有關的函數時,應指明語言,避免發生意外,正例"TITLE".toLowerCase(Locale.ENGLISH)

17.【強制在使用可能爲null引用的對象前,應判斷對象不爲null。避免出現空指針異常

18.【強制重定向後要結束程序,應該在重定向代碼後面寫其他的業務代碼,應該立即執行返回,避免額外代碼被利用的風險 。

19.【強制可序列化類中存在可序列化的敏感信息,對敏感信息字段使用transient關鍵字修飾,該字段將不會參與序列化。

20.【強制】JavaEE程序:遺留的調試代碼包括:入口函數main或@test發佈到正式環境的war中部應出現建議刪除調試遺留代碼

21.【強制日誌記錄使用系統輸出流, 避免程序中出現system.out或system.err等控制檯輸出語句,應使用日誌記錄工具代替

22.【強制public static字段必須聲明爲final,否則聲明爲private

23.【強制】使用equles進行字符串比較

24.【強制】如構造方法中需調用本實例方法,需將方法聲明爲final或private,避免被繼承,造成初始化構造方法時,子類未實例化;避免構造方法中調用可覆蓋的方法。

25.【強制】精確計算時建議將浮點型轉化爲整型進行計算,或使用bigdecimal進行計算

26.【強制】系統捕獲異常時不應該將異常信息直接輸出e.printStackTrace,應該使用日誌工具輸出具體的錯誤例如:log.error(“訂單異常”)

27.【強制】if表達式不可以出現永遠爲false或true

28.【強制】類中不應出現未使用的字段,未使用字段建議刪除

29.【強制】類中不應出現未使用的變量,未使用變量建議刪除

30.【強制】類中不應出現未使用的方法,未使用方法建議刪除,如果該方法爲預留方法,請在註釋中說明

31.【強制】避免重複的null檢查

32.【強制】採用判斷字符串長度的方法,來判斷字符串是否爲空,會提升程序性能

正例:.equals(“xxx”) 改爲 xxx.lenght()>0 判斷字符串是否爲空,

33.【強制】在循環中拼接字符串,應使用StringBuffer代替String

34.【強制】使用new String()創建String對象,會佔用額外的空間,降低性能;直接賦值java會在底層尋址充分利用字符串

正例String str1 = "abc";

35.【強制】無效的冗餘代碼,應該刪除

反例1

// 空代碼塊
if (i > 10) {
    空
}

反例2:

// 如果後續使用,請在TODO 中寫明用意,否則應該刪除
public void queryXX(String arg) {
    空
}

36.【強制】遺留的調試代碼,調試代碼應避免提交到svn,從而發佈到正式環境

37.【強制】通過有權限的類更改訪問權限修飾符,並確保修改的訪問權限修飾符參數不能被攻擊者控制;

38.【強制】前端輸入數據,要在前端校驗同時也需要在後臺校驗

39.【強制】程序中所需密碼從配置文件獲取經過加密的密碼值

說明:jdbc配置文件中 數據庫密碼是明文或者爲空;

40.【強制】應用程序註釋中不應保留密碼等敏感信息。同時禁止使用password、pass、pwd等敏感字作爲變量名、表單名或者字段名

反例:

//password = 123456

41.【強制】finally塊中的不應出現return語句,會導致 try塊中拋出的異常丟失,要求刪除finally語句塊中的return語句

42.【強制】異常捕獲時異常儘量準確,不應直接拋出Exception或者捕獲Exception;應儘量使用其子類,避免泛化的捕獲異常

43.【強制】在finally代碼塊中拋出異常時會消除從try或catch程序段拋出的任何異常,並影響後續代碼的執行,不應在finally代碼塊中拋出異常,應直接處理掉可能發生的異常

44.【強制】程序異常中斷了預期中的程序流程,應恰當地進行異常處理,要麼從異常中恢復,將異常重新拋出,由下一個和try語句對應的catch段來處理,要麼根據catch程序段的上下文拋出另一個合適的異常,避免空的catch代碼塊

45.【強制】程序不應依賴於finalize()回 收 流資源,應在finally代碼塊中手動釋放流資源。下面代碼片段中,在finally 代碼塊中對流資源進行了合理的釋放

46.【強制】忽略方法的返回值會導致程序無法發現意外狀況。應確保方法返回的是期望的數據

47.【強制】程序應對可能返回null的方法的返回值進行檢查,避免產生NullPointException

48.【強制】應用程序所解析的JSON數據來源於不可信賴的數據源,程序沒有對這些不可信賴的數據進行驗證、過濾,如果應用程序使用未經驗證的輸入構造 JSON,則可以更改 JSON 數據的語義避免JSON注入

正例

① 驗證json的結構是正確,沒有多餘的keyvalue;

② 驗證 json的內容是否有不合理的值,例如xss;

③ 驗證 發送請求是合法的;

④ 也可以使用 JsonStringEncoder  json字符串轉碼確保安全;

49.【強制】功能編碼過程中,要時刻注意防範sql注入

防範措施:

where 後面條件 都交給框架拼接(?佔位符或者在mapper循環)不要自己拼接sql字符串

② sql中部要出現一些永爲真的條件 1=1;

③ 根據項目整理一個sql指令白名單,如果攔截的sql語句中包含白名單中部存在的指令視爲 不合法sql;

50.【強制】設計po類的時候注意敏感字段的使用,避免直接綁定敏感字段

正例

在實體類  不要出現 name username  password   role  這些敏感字段特別是在po將這些字段一些別的名字

51.【強制】路徑遍歷

說明:

目錄遍歷(路徑遍歷)是由於Web服務器或者Web應用程序對用戶輸入的文件名稱的安全性驗證不足而導致的一種安全漏洞,使得攻擊者通過利用一些特殊字符就可以繞過服務器的安全限制,訪問任意的文件(可以是Web根目錄以外的文件),甚至執行系統命令。該漏洞常常出現在文件讀取或者展示圖片等對文件讀取交互的功能塊。

解決辦法:

① 對用戶的輸入進行驗證,特別是路徑替代字符如“../”和“~/”;

② 儘可能採用白名單的形式,驗證所有的輸入;

③ 合理配置Web服務器的目錄權限;

④ 當程序出錯時,不要顯示內部相關配置細節;

⑤ 對用戶傳過來的文件名參數進行統一編碼,對包含惡意字符或者空字符的參數進行拒絕;

52.【強制】使用重定向時,重定向的地址不能是外部傳入

 servletresponse.sendirect(URL)    這個url 儘量不要是用戶帶過來的參數

② 制定重定向白名單,非白名單中地址都不允許跳轉;

③ 重定向的地址定義到屬性文件中,需要重定向時到文件中讀取對應的地址;

53.【強制】安全的框架綁定

說明:

當框架把前端頁面中的輸入信息轉化成一個 實體類 傳給controller的一個方法,有的時候我們會直接 實體類傳給 service 層 增/改的方法,這這個實體類存在數據被篡改的風險。

解決辦法

① 不傳實體類 在controller拼成實體類或者傳map;

② 對實體類的內容驗證,確保是安全的.

54.【強制】http響應截斷

說明:

HTTP響應截斷是由於應用程序未對用戶提交的數據進行嚴格過濾,當用戶惡意提交包含 CR(回車,即URL編碼%0d或\r)和 LF(換行符,即URL編碼%0a或\n)的HTTP請求,服務器可能會創建兩個 HTTP 響應,攻擊者可以控制第二個響應並加載攻擊。攻擊者可控制響應的內容構造 XSS 攻擊,其中響應中包含惡意的 JavaScript 或其它代碼在用戶的瀏覽器中執行,也有可能讓用戶重定向到攻擊者控制的Web內容或在用戶的主機上執行惡意操作。本篇文章以JAVA語言源代碼爲例,分析HTTP響應截斷漏洞產生的原因以及修復方法。

正例

(1)對用戶的輸入進行合理驗證,對特殊字符(如<、>、’、”等)等進行編碼。

(2)創建一份安全字符白名單,只接受完全由這些受認可的字符組成的輸入出現在 HTTP 響應頭文件中。

(3)使用源代碼靜態分析工具,進行自動化的檢測,可以有效的發現源代碼中的 HTTP 響應截斷問題。

55.【強制】javascript 劫持

正例

(1) referer 驗證

(2)token 驗證

(3)儘量不要直接執行javascript的響應結果

56.【強制】加密密鑰不要在java代碼中直接定義死 例如  xxxkey =’123456789ABCDE’;這樣如果有人獲得這個class文件的話會反編譯得到這個密鑰

正例:

建議 在初始化類的時候動態生成一組隨機數

57.【強制】不受保護的cookie

正例

把重要的信息 緩存到cookie中的時候,對value進行加密

58.【強制】當使用StringBuilder接收數據是,首先要判斷數據大小

正例

StringBuilder. Append() 接收數據的時候   對這個數的大限進行判斷,如果這個值過大可拒絕執行操作

59.【強制】無論是前端發送數據到後臺,還是後臺查詢數據庫中的信息,敏感信息一律不許出現明文

60.【強制】登錄功能驗證碼漏洞

說明

① 前端首先對密碼加密

② 一個通過request參數獲得,另一個可以放到http請求頭的其它地方

③ controller中 首先對比着兩個加密的字符串是否一致防止請求過程中被篡改;

④ 數據庫中有兩種表存儲密碼,獲取兩種表裏的密碼 在controller中驗證以否一致

⑤ jsp頁面的密碼解密(也可不解密看設計)然後用一種加密方式加密,最後使用js帶過來的密碼 與數據庫中的做對比

 

六、Mysql數據庫

(一)建表規範

1.【強制】表達是與否概念的字段,必須使用 is_xxx的方式命名,數據類型是 unsigned tinyint1 表示是,0 表示否)。

說明:任何字段如果爲非負數,必須是 unsigned

注意:POJO類中的任何布爾類型的變量,都不要加 is前綴,所以,需要在<resultMap>設置從 is_xxx Xxx的映射關係。數據庫表示是與否的值,使用 tinyint類型,堅持 is_xxx的命名方式是爲了明確其取值含義與取值範圍。

正例:表達邏輯刪除的字段名 is_deleted1 表示刪除,0 表示未刪除。

2.【強制】表名、字段名必須使用小寫字母或數字禁止出現數字開頭,禁止兩個下劃線中間只出現數字。數據庫字段名的修改代價很大,因爲無法進行預發佈,所以字段名稱需要慎重考慮。

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

 正例:powerpeak _adminrdc_configlevel3_name 反例:Powerpeak AdminrdcConfiglevel_3_name

3.【強制】表名不使用複數名詞。

說明:表名應該僅僅表示表裏面的實體內容,不應該表示實體數量,對應於 DO類名也是單數形式,符合表達習慣。

4.【強制】禁用保留字,如 descrangematchdelayed等,請參考 MySQL官方保留字。

5.【強制】主鍵索引名爲 pk_字段名;唯一索引名爲 uk_字段名;普通索引名則爲 idx_字段名。

說明:pk_  primary keyuk_  unique keyidx_  index的簡稱。

6.【強制】小數類型爲 decimal,禁止使用 float double

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

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

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

9.【強制】表必備三字段:id, gmt_create, gmt_modified

說明:其中 id必爲主鍵,類型爲 bigint unsigned、單表時自增、步長爲 1gmt_create,gmt_modified的類型均爲 datetime類型,前者現在時表示主動創建,後者過去分詞表示被動更新。

10.【推薦】表的命名最好是加上業務名稱_表的作用

正例:powerpeak_task / force_project / trade_config

11.【推薦】庫名與應用名稱儘量一致。

12.【推薦】如果修改字段含義或對字段表示的狀態追加時,需要及時更新字段註釋。

13.【推薦】字段允許適當冗餘,以提高查詢性能,但必須考慮數據一致。冗餘字段應遵循:

1不是頻繁修改的字段。

2不是 varchar超長字段,更不能是 text字段。

正例:商品類目名稱使用頻率高,字段長度短,名稱基本一成不變,可在相關聯的表中冗餘存儲類目名稱,避免關聯查詢。

14.【推薦】單錶行數超過 500萬行或者單表容量超過 2GB,才推薦進行分庫分表。

說明:如果預計三年後的數據量根本達不到這個級別,請不要在創建表時就分庫分表。

15.【參考】合適的字符存儲長度,不但節約數據庫表空間、節約索引存儲,更重要的是提升檢索速度。

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

對象

年齡區間

類型

字節

表示範圍

150歲之內

tinyint unsigned

1

無符號值:0 255

數百歲

smallint unsigned

2

無符號值:0 65535

恐龍化石

數千萬年

int unsigned

4

無符號值:0到約 42.9

太陽

 50億年

bigint unsigned

8

無符號值:0到約 10 19次方

 

(二)索引規範

1.【強制】業務上具有唯一特性的字段,即使是多個字段的組合,也必須建成唯一索引。

說明:不要以爲唯一索引影響了 insert速度,這個速度損耗可以忽略,但提高查找速度是明顯的另外,即使在應用層做了非常完善的校驗控制,只要沒有唯一索引,根據墨菲定律,必然有髒數據產生。

2.【強制】超過三個表禁止 join。需要 join的字段,數據類型必須絕對一致多表關聯查詢時,保證被關聯的字段需要有索引。

說明:即使雙表 join也要注意表索引、SQL性能。

3.【強制】 varchar字段上建立索引時,必須指定索引長度,沒必要對全字段建立索引,根據實際文本區分度決定索引長度即可。 

說明:索引的長度與區分度是一對矛盾體,一般對字符串類型數據,長度爲 20的索引,區分度會高達 90%以上,可以使用 count(distinct left(列名, 索引長度))/count(*)的區分度 來確定。

4.【強制】頁面搜索嚴禁左模糊或者全模糊,如果需要請走搜索引擎來解決。

說明:索引文件具有 B-Tree的最左前綴匹配特性,如果左邊的值未確定,那麼無法使用此索引。

5.【推薦】如果有 order by的場景,請注意利用索引的有序性order by 最後的字段是組合索引的一部分,並且放在索引組合順序的最後,避免出現 file_sort的情況,影響查詢性能。正例:where a=? and b=? order by c; 索引:a_b_c

反例:索引中有範圍查找,那麼索引有序性無法利用,如:WHERE a>10 ORDER BY b; 索引a_b無法排序。

6.【推薦】利用覆蓋索引來進行查詢操作,避免回表。

說明:如果一本書需要知道第 11章是什麼標題,會翻開第 11章對應的那一頁嗎?目錄瀏覽一下就好,這個目錄就是起到覆蓋索引的作用。

正例:能夠建立索引的種類分爲主鍵索引、唯一索引、普通索引三種,而覆蓋索引只是一種查詢的一種效果,用 explain的結果,extra列會出現:using index

7.【推薦】利用延遲關聯或者子查詢優化超多分頁場景。

說明:MySQL並不是跳過 offset行,而是取 offset+N行,然後返回放棄前 offset行,返回N行,那當 offset特別大的時候,效率就非常的低下,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行 SQL改寫。

正例:先快速定位需要獲取的 id段,然後再關聯:SELECT a.* FROM  1 a, (select id from  1 where 條件 LIMIT 100000,20 ) b where a.id=b.id

8.【推薦】SQL性能優化的目標:至少要達到 range 級別,要求是 ref級別,如果可以是 consts最好。

說明:

1consts 單表中最多隻有一個匹配行主鍵或者唯一索引,在優化階段即可讀取到數據。

2ref 指的是使用普通的索引normal index

3range 對索引進行範圍檢索。

反例:explain表的結果,type=index,索引物理文件全掃描,速度非常慢,這個 index級別比較 range還低,與全表掃描是小巫見大巫。

9.【推薦】建組合索引的時候,區分度最高的在最左邊。

正例:如果 where a=? and b=? ,如果 a列的幾乎接近於唯一值,那麼只需要單建 idx_a索引即可。

說明:存在非等號和等號混合時,在建索引時,請把等號條件的列前置。

如:where c>? andd=? 那麼即使 c的區分度更高,也必須把 d放在索引的最前列,即索引 idx_d_c

10.【推薦】防止因字段類型不同造成的隱式轉換,導致索引失效。

11.【參考】創建索引時避免有如下極端誤解:

1寧濫勿缺。認爲一個查詢就需要建一個索引。

2寧缺勿濫。認爲索引會消耗空間、嚴重拖慢更新和新增速度。

3)抵制惟一索引。認爲業務的惟一性一律需要在應用層通過“先查後插”方式解決。

(三)SQL語句

1.【強制】不要使用 count(列名) count(常量)來替代 count(*)count(*) SQL92定義的標準統計行數的語法,跟數據庫無關,跟 NULL和非 NULL無關。

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

2.【強制】count(distinct col) 計算該列除 NULL之外的不重複行數,注意count(distinct col1, col2) 如果其中一列全爲 NULL,那麼即使另一列有不同的值,也返回爲 0

3.【強制】當某一列的值全是 NULL時,count(col)的返回結果爲 0,但 sum(col)的返回結果爲NULL,因此使用 sum()時需注意 NPE問題。

正例:可以使用如下方式來避免 sum NPE問題:SELECT IF(ISNULL(SUM(g)),0,SUM(g))FROM table;

4.【強制】使用 ISNULL()來判斷是否爲 NULL值。

說明:NULL與任何值的直接比較都爲 NULL

1 NULL<>NULL的返回結果是 NULL,而不是 false

2 NULL=NULL的返回結果是 NULL,而不是 true

3 NULL<>1的返回結果是 NULL,而不是 true

5.【強制】 在代碼中寫分頁查詢邏輯時,若 count 0應直接返回,避免執行後面的分頁語句。

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

說明:以學生和成績的關係爲例,學生表中的 student_id是主鍵,那麼成績表中的 student_id則爲外鍵。如果更新學生表中的 student_id,同時觸發成績表中的 student_id更新,即爲 級聯更新。外鍵與級聯更新適用於單機低併發,不適合分佈式、高併發集羣級聯更新是強阻 塞,存在數據庫更新風暴的風險外鍵影響數據庫的插入速度。

7.【強制】禁止使用存儲過程,存儲過程難以調試和擴展,更沒有移植性。

8.【強制】數據訂正(特別是刪除、修改記錄操作)時,要先 select,避免出現誤刪除,確認無誤才能執行更新語句。

9.【推薦】in操作能避免則避免,若實在避免不了,需要仔細評估 in後邊的集合元素數量,控制在 1000個之內。 

10.【參考】如果有國際化需要,所有的字符存儲與表示,均以 utf-8編碼,注意字符統計函數的區別。

說明:

SELECT LENGTH("輕鬆工作") 返回爲 12

SELECT CHARACTER_LENGTH("輕鬆工作") 返回爲 4

如果需要存儲表情,那麼選擇 utf8mb4來進行存儲,注意它與 utf-8編碼的區別。

11.【參考】TRUNCATE TABLE  DELETE 速度快,且使用的系統和事務日誌資源少,但 TRUNCATE無事務且不觸發 trigger,有可能造成事故,故不建議在開發代碼中使用此語句。

說明:TRUNCATE TABLE 在功能上與不帶 WHERE 子句的 DELETE 語句相同。

(四)ORM映射

1.【強制】在表查詢中,一律不要使用 * 作爲查詢的字段列表,需要哪些字段必須明確寫明。

說明:

1增加查詢分析器解析成本。

2增減字段容易與 resultMap配置不一致。

3)無用字段增加網絡消耗,尤其是 text類型的字段。

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

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

3.【強制】不要用 resultClass當返回參數,即使所有類屬性名與數據庫字段一一對應,也需要定義反過來,每一個表也必然有一個 POJO類與之對應。

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

4.【強制】sql.xml配置參數使用:#{}#param# 不要使用${} 此種方式容易出現 SQL注入。

5.【強制】iBATIS自帶的 queryForList(String statementName,int start,int size)不推薦使用。

說明:其實現方式是在數據庫取到statementName對應的SQL語句的所有記錄,再通過subList start,size的子集合。

正例:

Map<String, Object> map = new HashMap<>();

map.put("start", start);map.put("size", size);

6.【強制】不允許直接拿 HashMap Hashtable作爲查詢結果集的輸出。

說明:resultClass=”Hashtable”,會置入字段名和屬性值,但是值的類型不可控。

7.【強制】更新數據表記錄時,必須同時更新記錄對應的 gmt_modified字段值爲當前時間。 

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

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

10.【參考】<isEqual>中的 compareValue是與屬性值對比的常量,一般是數字,表示相等時帶上此條件<isNotEmpty>表示不爲空且不爲 null時執行<isNotNull>表示不爲 null值時執行。

 

更多精彩,請關注我的"今日頭條號":Java雲筆記
隨時隨地,讓你擁有最新,最便捷的掌上雲服務

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章