回覆列表
  • 1 # 風信子視野

    一、MyBatis 不要為了多個查詢條件而寫 1 = 1

    當遇到多個查詢條件,使用 where 1=1 可以很方便的解決我們的問題,但是這樣很可能會造成非常大的效能損失,因為添加了 “where 1=1” 的過濾條件之後,資料庫系統就無法使用索引等查詢最佳化策略,資料庫系統將會被迫對每行資料進行掃描(即全表掃描) 以比較此行是否滿足過濾條件,當表中的資料量較大時查詢速度會非常慢;此外,還會存在 SQL 注入的風險。

    反例:

    <select parameterType="com.tjt.platform.entity.BookInfo" resultType="java.lang.Integer">

    select count(*) from t_rule_BookInfo t where 1=1

    <if test="title !=null and title !="" ">

    AND title = #{title}

    </if>

    <if test="author !=null and author !="" ">

    AND author = #{author}

    </if>

    </select>複製程式碼

    正例:

    <select parameterType="com.tjt.platform.entity.BookInfo" resultType="java.lang.Integer">

    select count(*) from t_rule_BookInfo t

    <where>

    <if test="title !=null and title !="" ">

    title = #{title}

    </if>

    <if test="author !=null and author !="" ">

    AND author = #{author}

    </if>

    </where>

    </select>複製程式碼UPDATE 操作也一樣,可以用 <set> 標記代替 1=1。

    二、 迭代 entrySet() 獲取 Map 的 key 和 value

    當迴圈中只需要獲取 Map 的主鍵 key 時,迭代 keySet() 是正確的;但是,當需要主鍵 key 和取值 value 時,迭代 entrySet() 才是更高效的做法,其比先迭代 keySet() 後再去透過 get 取值效能更佳。

    反例:

    //Map 獲取value 反例:

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

    for (String key : map.keySet()){

    String value = map.get(key);

    }複製程式碼

    正例:

    //Map 獲取key & value 正例:

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

    for (Map.Entry<String,String> entry : map.entrySet()){

    String key = entry.getKey();

    String value = entry.getValue();

    三、使用Collection.isEmpty() 檢測空

    使用 Collection.size() 來檢測是否為空在邏輯上沒有問題,但是使用 Collection.isEmpty() 使得程式碼更易讀,並且可以獲得更好的效能;除此之外,任何 Collection.isEmpty() 實現的時間複雜度都是 O(1) ,不需要多次迴圈遍歷,但是某些透過 Collection.size() 方法實現的時間複雜度可能是 O(n)。O(1) 緯度減少迴圈次數 例子

    反例:

    LinkedList<Object> collection = new LinkedList<>();

    if (collection.size() == 0){

    System.out.println("collection is empty.");

    }複製程式碼

    正例:

    LinkedList<Object> collection = new LinkedList<>();

    if (collection.isEmpty()){

    System.out.println("collection is empty.");

    }

    //檢測是否為null 可以使用CollectionUtils.isEmpty()

    if (CollectionUtils.isEmpty(collection)){

    System.out.printl

  • 2 # 主引教程

    第一步,簡單規範制定;

    第二步,程式碼案例,並生成文件;

    第三步,規範+案例 團隊集中學習;

    第四步,團隊自我完善規範——迴圈;

    實踐建議:網路上規範一大堆,有一個基本的JAVA規範,沒二話大家都可以實現。業務程式碼功能模組化生成規範。

  • 3 # 熙熙爸育兒

    一、程式設計規約

    (一) 命名風格

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

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

    2. 【強制】程式碼中的命名嚴禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。說明:正確的英文拼寫和語法可以讓閱讀者易於理解,避免歧義。注意,即使純拼音命名方式也要避免採用。正例:alibaba / 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

    4. 【強制】方法名、引數名、成員變數、區域性變數都統一使用lowerCamelCase風格,必須遵從駝峰形式。

    正例: localValue / getHttpMessage() / inputUserId

    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.alibaba.ai.util、類名為MessageUtils(此規則參考spring 的框架結構)

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

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

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

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

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

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

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

    正例:public class OrderFactory; public class LoginProxy;

    public class ResourceObserver;

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

    正例:介面方法簽名void commit();

    介面基礎常量String COMPANY = "alibaba";

    反例:介面方法定義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做字首。

    6) 修改的方法用update做字首。

    B) 領域模型命名規約

    1) 資料物件:xxxDO,xxx即為資料表名。

    2) 資料傳輸物件:xxxDTO,xxx為業務領域相關的名稱。

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

    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型的2?

    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;

    }

    }

    (三) 程式碼格式

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

    1) 左大括號前不換行。

    2) 左大括號後換行。

    3) 右大括號前換行。

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

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

    空格

    a == b

    空格

    反例:if ()

    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這個變數,如果需要對齊,則給a、b、c都要增加幾個空格,在變數比較多的情況下,是非常累贅的事情。

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

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

    (四) 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#equals(JDK7引入的工具類)

    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。

    說明:任何類、方法、引數、變數,嚴控訪問範圍。過於寬泛的訪問範圍,不利於模組解耦。

    (五) 集合處理

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

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

    2) 因為Set儲存的是不重複的物件,依據hashCode和equals進行判斷,所以Set儲存的物件必須重寫這兩個方法。

    3) 如果自定義物件作為Map的鍵,那麼必須重寫hashCode和equals。

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

    2. 【強制】 ArrayList的subList結果不可強轉成ArrayList,否則會丟擲ClassCastException

    異常,即java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。說明:subList 返回的是 ArrayList 的內部類 SubList,並不是 ArrayList而是ArrayList 的一個檢視,對於SubList子列表的所有操作最終會反映到原列表上。

    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.sort,

    Collections.sort會報IllegalArgumentException異常。

    說明:三個條件如下

    1) x,y的比較結果和y,x的比較結果相反。

    2) x>y,y>z,則x>z。

    3) x=y,則x,z比較結果和y,z比較結果相同。

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

    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。注意負載因子(即loader factor)預設為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/unsort;HashMap是unorder/unsort;TreeSet是 order/sort。

    14. 【參考】利用Set元素唯一的特性,可以快速對一個集合進行去重操作,避免使用List的

    contains方法進行遍歷、對比、去重操作。

    (六) 併發處理

    1. 【強制】獲取單例物件需要保證執行緒安全,其中的方法也要保證執行緒安全。說明:資源驅動類、工具類、單例工廠類都需要注意。

    2. 【強制】建立執行緒或執行緒池時請指定有意義的執行緒名稱,方便出錯時回溯。

    正例:

    public class TimerTaskThread extends Thread { public TimerTaskThread() { super.setName("TimerTaskThread");

    ...

    }

    }

    3. 【強制】執行緒資源必須透過執行緒池提供,不允許在應用中自行顯式建立執行緒。

    說明:使用執行緒池的好處是減少在建立和銷燬執行緒上所消耗的時間以及系統資源的開銷,解決資源不足的問題。如果不使用執行緒池,有可能造成系統建立大量同類執行緒而導致消耗完記憶體或者“過度切換”的問題。

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

    說明:Executors返回的執行緒池物件的弊端如下:

    1)FixedThreadPool和SingleThreadPool:

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

    2)CachedThreadPool和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代替Date,LocalDateTime代替Calendar,

    DateTimeFormatter代替SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable thread-safe。

    6. 【強制】高併發時,同步呼叫應該去考量鎖的效能損耗。能用無鎖資料結構,就不要用鎖;能鎖區塊,就不要鎖整個方法體;能用物件鎖,就不要用類鎖。

    說明:儘可能使加鎖的程式碼塊工作量儘可能的小,避免在鎖程式碼塊中呼叫 RPC 方法。

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

    說明:執行緒一需要對錶A、B、C依次全部加鎖後才可以進行更新操作,那麼執行緒二的加鎖順序也必須是A、B、C,否則可能出現死鎖。

    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 Alibaba 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. 【推薦】程式碼修改的同時,註釋也要進行相應的修改,尤其是引數、返回值、異常、核心邏輯等的修改。

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

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

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

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

    反例:

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

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

    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. 【推薦】及時清理不再使用的程式碼段或配置資訊。

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

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

    二、異常日誌

    (一) 異常處理

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

    說明:無法透過預檢查的異常除外,比如,在解析字串形式的數字時,不得不透過catch

    NumberFormatException來實現。

    正例: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. 【參考】避免出現重複的程式碼(Don’t 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.log。

    logType:日誌型別,如stats/monitor/access等;logName:日誌描述。這種命名的好處:透過檔名就可知道日誌檔案屬於什麼應用,什麼型別,什麼目的,也有利於歸類查詢。

    正例: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);

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

    8. 【推薦】可以使用 warn 日誌級別來記錄使用者輸入引數錯誤的情況,避免使用者投訴時,無所適

    從。如非必要,請不要在此場景打出 error 級別,避免頻繁報警。

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

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

    三、單元測試

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

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

    l A:Automatic(自動化)

    l I:Independent(獨立性)

    l R:Repeatable(可重複)

    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 B:Border,邊界值測試,包括迴圈邊界、特殊取值、特殊時間點、資料順序等。

    l C:Correct,正確的輸入,並得到預期的結果。

    l D:Design,與設計文件相結合,來編寫單元測試。

    l E:Error,強制錯誤資訊輸入(如:非法資料、異常流程、非業務允許輸入等),並得到預期的結果。

    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. 【強制】使用者請求傳入的任何引數必須做有效性驗證。

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

    l page size過大導致記憶體溢位

    l 惡意order by導致資料庫慢查詢

    l 任意重定向

    l SQL注入

    l 反序列化注入

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

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

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

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

    說明:CSRF(Cross-site request forgery)跨站請求偽造是一類常見程式設計漏洞。對於存在

    CSRF漏洞的應用/網站,攻擊者可以事先構造好URL,只要受害者使用者一訪問,後臺便在使用者不知情的情況下對資料庫中使用者引數進行相應修改。

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

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

    五、MySQL資料庫

    (一) 建表規約

    1. 【強制】表達是與否概念的欄位,必須使用is_xxx的方式命名,資料型別是unsigned tinyint

    (1 表示是,0 表示否)。

    說明:任何欄位如果為非負數,必須是unsigned。

    注意:POJO類中的任何布林型別的變數,都不要加is字首,所以,需要在<resultMap>設定從is_xxx到Xxx的對映關係。資料庫表示是與否的值,使用tinyint型別,堅持is_xxx的命名方式是為了明確其取值含義與取值範圍。

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

    說明:MySQL在Windows下不區分大小寫,但在 Linux 下預設是區分大小寫。因此,資料庫名、表名、欄位名,都不允許出現任何大寫字母,避免節外生枝。

    正例:aliyun_admin,rdc_config,level3_name 反例:AliyunAdmin,rdcConfig,level_3_name

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

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

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

    5. 【強制】主鍵索引名為pk_欄位名;唯一索引名為uk_欄位名;普通索引名則為idx_欄位名。

    說明:pk_ 即primary key;uk_ 即 unique key;idx_ 即index的簡稱。

    6. 【強制】小數型別為decimal,禁止使用float和double。

    說明:float和double在儲存的時候,存在精度損失的問題,很可能在值的比較時,得到不

    正確的結果。如果儲存的資料範圍超過decimal的範圍,建議將資料拆成整數和小數分開儲存。

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

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

    9. 【強制】表必備三欄位:id, gmt_create, gmt_modified。

    說明:其中id必為主鍵,型別為bigint unsigned、單表時自增、步長為 1。gmt_create,

    gmt_modified的型別均為datetime型別,前者現在時表示主動建立,後者過去分詞表示被動更新。

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

    正例:alipay_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 最好。說明:

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

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

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

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

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

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

    說明:存在非等號和等號混合時,在建索引時,請把等號條件的列前置。如:where c>? and d=? 那麼即使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. 【強制】禁止使用儲存過程,儲存過程難以除錯和擴充套件,更沒有移植性。

    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值時執行。

    六、工程結構

    (一) 應用分層

    1. 【推薦】圖中預設上層依賴於下層,箭頭關係表示可直接依賴,如:開放介面層可以依賴於

    Web層,也可以直接依賴於Service層,依此類推:

    • 開放介面層:可直接封裝Service方法暴露成RPC介面;透過Web封裝成http介面;進行閘道器安全控制、流量控制等。

    • 終端顯示層:各個端的模板渲染並執行顯示的層。當前主要是velocity渲染,JS渲染,

    JSP渲染,移動端展示等。

    • Service層:相對具體的業務邏輯服務層。

    • Manager層:通用業務處理層,它有如下特徵:

    1) 對第三方平臺封裝的層,預處理返回結果及轉化異常資訊;

    2) 對Service層通用能力的下沉,如快取方案、中介軟體通用處理;

    3) 與DAO層互動,對多個DAO的組合複用。

    • DAO層:資料訪問層,與底層MySQL、Oracle、Hbase等進行資料互動。

    • 外部介面或第三方平臺:包括其它部門RPC開放介面,基礎平臺,其它公司的HTTP介面。

    2. 【參考】 (分層異常處理規約)在DAO層,產生的異常型別有很多,無法用細粒度的異常進

    行catch,使用catch(Exception e)方式,並throw new DAOException(e),不需要列印日誌,因為日誌在Manager/Service層一定需要捕獲並列印到日誌檔案中去,如果同臺伺服器再打日誌,浪費效能和儲存。在Service層出現異常時,必須記錄出錯日誌到磁碟,儘可能帶上引數資訊,相當於保護案發現場。如果Manager層與Service同機部署,日誌方式與DAO 層處理一致,如果是單獨部署,則採用與Service一致的處理方式。Web層絕不應該繼續往上拋異常,因為已經處於頂層,如果意識到這個異常將導致頁面無法正常渲染,那麼就應該直接跳轉到友好錯誤頁面,加上使用者容易理解的錯誤提示資訊。開放介面層要將異常處理成錯誤碼和錯誤資訊方式返回。

    3. 【參考】分層領域模型規約:

    • DO(Data Object):此物件與資料庫表結構一一對應,透過DAO層向上傳輸資料來源物件。

    • DTO(Data Transfer Object):資料傳輸物件,Service或Manager向外傳輸的物件。

    • BO(Business Object):業務物件,由Service層輸出的封裝業務邏輯的物件。

    • AO(Application Object):應用物件,在Web層與Service層之間抽象的複用物件模型,極為貼近展示層,複用度不高。

    • VO(View Object):顯示層物件,通常是Web向模板渲染引擎層傳輸的物件。

    • Query:資料查詢物件,各層接收上層的查詢請求。注意超過2個引數的查詢封裝,禁止使用Map類來傳輸。

    (二) 二方庫依賴

    1. 【強制】定義GAV遵從以下規則:

    1) GroupID格式:com.{公司/BU }.業務線 [.子業務線],最多4級。

    說明:{公司/BU} 例如:alibaba/taobao/tmall/aliexpress等BU一級;子業務線可選。

    正例:com.taobao.jstorm 或 com.alibaba.dubbo.register

    2) ArtifactID格式:產品線名-模組名。語義不重複不遺漏,先到中央倉庫去查證一下。

    正例:dubbo-client / fastjson-api / jstorm-tool

    3) Version:詳細規定參考下方。

    2. 【強制】二方庫版本號命名方式:主版本號.次版本號.修訂號

    1) 主版本號:產品方向改變,或者大規模API不相容,或者架構不相容升級。

    2) 次版本號:保持相對相容性,增加主要功能特性,影響範圍極小的API不相容修改。

    3) 修訂號:保持完全相容性,修復BUG、新增次要功能特性等。

    說明:注意起始版本號必須為:1.0.0,而不是0.0.1 正式釋出的類庫必須先去中央倉庫進行查證,使版本號有延續性,正式版本號不允許覆蓋升級。如當前版本:1.3.3,那麼下一個合理的版本號:1.3.4 或 1.4.0 或 2.0.0

    3. 【強制】線上應用不要依賴SNAPSHOT版本(安全包除外)。

    說明:不依賴SNAPSHOT版本是保證應用釋出的冪等性。另外,也可以加快編譯時的打包構建。

    4. 【強制】二方庫的新增或升級,保持除功能點之外的其它jar包仲裁結果不變。如果有改變,

    必須明確評估和驗證,建議進行dependency:resolve前後資訊比對,如果仲裁結果完全不一致,那麼透過dependency:tree命令,找出差異點,進行<excludes>排除jar包。

    5. 【強制】二方庫裡可以定義列舉型別,引數可以使用列舉型別,但是介面返回值不允許使用列舉型別或者包含列舉型別的POJO物件。

    6. 【強制】依賴於一個二方庫群時,必須定義一個統一的版本變數,避免版本號不一致。

    說明:依賴springframework-core,-context,-beans,它們都是同一個版本,可以定義一個變數來儲存版本:${spring.version},定義依賴的時候,引用該版本。

    7. 【強制】禁止在子專案的pom依賴中出現相同的GroupId,相同的ArtifactId,但是不同的

    Version。

    說明:在本地除錯時會使用各子專案指定的版本號,但是合併成一個war,只能有一個版本號出現在最後的lib目錄中。可能出現線下除錯是正確的,釋出到線上卻出故障的問題。

    8. 【推薦】所有pom檔案中的依賴宣告放在<dependencies>語句塊中,所有版本仲裁放在

    <dependencyManagement>語句塊中。

    說明:<dependencyManagement>裡只是宣告版本,並不實現引入,因此子專案需要顯式的聲

    明依賴,version和scope都讀取自父pom。而<dependencies>所有宣告在主pom的

    <dependencies>裡的依賴都會自動引入,並預設被所有的子專案繼承。

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

    10. 【參考】為避免應用二方庫的依賴衝突問題,二方庫釋出者應當遵循以下原則:

    1) 精簡可控原則。移除一切不必要的API和依賴,只包含 Service API、必要的領域模型物件、Utils類、常量、列舉等。如果依賴其它二方庫,儘量是provided引入,讓二方庫使用者去依賴具體版本號;無log具體實現,只依賴日誌框架。

    2) 穩定可追溯原則。每個版本的變化應該被記錄,二方庫由誰維護,原始碼在哪裡,都需要能方便查到。除非使用者主動升級版本,否則公共二方庫的行為不應該發生變化。

    (三) 伺服器

    1. 【推薦】高併發伺服器建議調小TCP協議的time_wait超時時間。

    說明:作業系統預設240秒後,才會關閉處於time_wait狀態的連線,在高併發訪問下,伺服器端會因為處於time_wait的連線數太多,可能無法建立新的連線,所以需要在伺服器上調小此等待值。

    正例:在linux伺服器上請透過變更/etc/sysctl.conf檔案去修改該預設值(秒): net.ipv4.tcp_fin_timeout = 30

    2. 【推薦】調大伺服器所支援的最大檔案控制代碼數(File Descriptor,簡寫為fd)。

    說明:主流作業系統的設計是將TCP/UDP連線採用與檔案一樣的方式去管理,即一個連線對應於一個fd。主流的linux伺服器預設所支援最大fd數量為1024,當併發連線數很大時很容易因為fd不足而出現“open too many files”錯誤,導致新的連線無法建立。 建議將linux 伺服器所支援的最大控制代碼數調高數倍(與伺服器的記憶體數量相關)。

    3. 【推薦】給JVM環境引數設定-XX:+HeapDumpOnOutOfMemoryError引數,讓JVM碰到OOM場景時輸出dump資訊。

    說明:OOM的發生是有機率的,甚至相隔數月才出現一例,出錯時的堆內資訊對解決問題非常有幫助。

    4. 【推薦】在線上生產環境,JVM的Xms和Xmx設定一樣大小的記憶體容量,避免在GC 後調整堆大小帶來的壓力。

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

    七、設計規約

    1. 【強制】儲存方案和底層資料結構的設計獲得評審一致透過,並沉澱成為文件。

    說明:有缺陷的底層資料結構容易導致系統風險上升,可擴充套件性下降,重構成本也會因歷史資料遷移和系統平滑過渡而陡然增加,所以,儲存方案和資料結構需要認真地進行設計和評審,生產環境提交執行後,需要進行double check。

    正例:評審內容包括儲存介質選型、表結構設計能否滿足技術方案、存取效能和儲存空間能否滿足業務發展、表或欄位之間的辯證關係、欄位名稱、欄位型別、索引等;資料結構變更(如在原有表中新增欄位)也需要進行評審通過後上線。

    2. 【強制】在需求分析階段,如果與系統互動的User超過一類並且相關的User Case超過5個,使用用例圖來表達更加清晰的結構化需求。

    3. 【強制】如果某個業務物件的狀態超過3個,使用狀態圖來表達並且明確狀態變化的各個觸發條件。

    說明:狀態圖的核心是物件狀態,首先明確物件有多少種狀態,然後明確兩兩狀態之間是否存在直接轉換關係,再明確觸發狀態轉換的條件是什麼。

    正例:淘寶訂單狀態有已下單、待付款、已付款、待發貨、已發貨、已收貨等。比如已下單與已收貨這兩種狀態之間是不可能有直接轉換關係的。

    4. 【強制】如果系統中某個功能的呼叫鏈路上的涉及物件超過3個,使用時序圖來表達並且明確各呼叫環節的輸入與輸出。

    說明:時序圖反映了一系列物件間的互動與協作關係,清晰立體地反映系統的呼叫縱深鏈路。

    5. 【強制】如果系統中模型類超過5個,並且存在複雜的依賴關係,使用類圖來表達並且明確類之間的關係。

    說明:類影象建築領域的施工圖,如果搭平房,可能不需要,但如果建造螞蟻Z空間大樓,肯定需要詳細的施工圖。

    6. 【強制】如果系統中超過2個物件之間存在協作關係,並且需要表示複雜的處理流程,使用活動圖來表示。

    說明:活動圖是流程圖的擴充套件,增加了能夠體現協作關係的物件泳道,支援表示併發等。

    7. 【推薦】需求分析與系統設計在考慮主幹功能的同時,需要充分評估異常流程與業務邊界。反例:使用者在淘寶付款過程中,銀行扣款成功,傳送給使用者扣款成功簡訊,但是支付寶入款時由於斷網演練產生異常,淘寶訂單頁面依然顯示未付款,導致使用者投訴。

    8. 【推薦】類在設計與實現時要符合單一原則。

    說明:單一原則最易理解卻是最難實現的一條規則,隨著系統演進,很多時候,忘記了類設計的初衷。

    9. 【推薦】謹慎使用繼承的方式來進行擴充套件,優先使用聚合/組合的方式來實現。

    說明:不得已使用繼承的話,必須符合里氏代換原則,此原則說父類能夠出現的地方子類一定能夠出現,比如,“把錢交出來”,錢的子類美元、歐元、人民幣等都可以出現。

    10.【推薦】系統設計時,根據依賴倒置原則,儘量依賴抽象類與介面,有利於擴充套件與維護。

    說明:低層次模組依賴於高層次模組的抽象,方便系統間的解耦。

    11.【推薦】系統設計時,注意對擴充套件開放,對修改閉合。

    說明:極端情況下,交付的程式碼都是不可修改的,同一業務域內的需求變化,透過模組或類的擴充套件來實現。

    12.【推薦】系統設計階段,共性業務或公共行為抽取出來公共模組、公共配置、公共類、公共方法等,避免出現重複程式碼或重複配置的情況。

    說明:隨著程式碼的重複次數不斷增加,維護成本指數級上升。

    13. 【推薦】避免如下誤解:敏捷開發 = 講故事 + 編碼 + 釋出。

    說明:敏捷開發是快速交付迭代可用的系統,省略多餘的設計方案,摒棄傳統的審批流程,但核心關鍵點上的必要設計和文件沉澱是需要的。

    反例:某團隊為了業務快速發展,敏捷成了產品經理催進度的藉口,系統中均是勉強能執行但

    像麵條一樣的程式碼,可維護性和可擴充套件性極差,一年之後,不得不進行大規模重構,得不償失。

    14.【參考】系統設計主要目的是明確需求、理順邏輯、後期維護,次要目的用於指導編碼。

    說明:避免為了設計而設計,系統設計文件有助於後期的系統維護,所以設計結果需要進行分類歸檔儲存。

    15.【參考】設計的本質就是識別和表達系統難點,找到系統的變化點,並隔離變化點。

    說明:世間眾多設計模式目的是相同的,即隔離系統變化點。

    16. 【參考】系統架構設計的目的:

    l 確定系統邊界。確定系統在技術層面上的做與不做。

    l 確定系統內模組之間的關係。確定模組之間的依賴關係及模組的宏觀輸入與輸出。

    l 確定指導後續設計與演化的原則。使後續的子系統或模組設計在規定的框架內繼續演化。

    確定非功能性需求。非功能性需求是指安全性、可用性、可擴充套件性等。

  • 4 # 淞南北丁巷

    這個問題包含兩點:

    程式碼如何更加規範;程式碼如何更整潔;

    下面按照這兩點展開說說我的理解。

    程式碼規範

    程式碼規範其實業界大廠或者別的語言已經有很多解決方案了,比如,pylint,clang-format,go-fmt這種,對於JAVA可以試試看Google的google-java-format,我目前是gradle流,喜歡maven的朋友不妨自己去官網上看一下用法。

    引入的方法很簡單:

    直接在plugins裡宣告一下。

    檢查的話可以敲命令:gradle verifyGoogleJavaFormat

    按照Google模板改寫檔案:gradle googleJavaFormat

    當然最好在Makefile裡寫好,一勞永逸。

    這個格式也支援IDEA和eclipse,以IDEA為例:

    在外掛的Marketplace裡搜google-java-format,然後安裝。IntellJ IDEA -> Other Settings -> google-java-format 勾選Enable預設專案應用的話,在Files -> Other Settings -> Default Settings裡啟用

    應用完成後Ctrl+Alt+L就能格式化程式碼了。不過這裡有個坑,它不會格式化import程式碼段,這個可以去github上下載個格式匯入IDEA解決。

    這樣幾步下來基本就能保證團隊內部的程式碼風格一致。

    程式碼整潔

    程式碼整潔比較考驗基本功,最好的辦法是去看看Effective Java,按Joshua Bloch大神的要求做。

    這個話題三言兩語說不清,加油吧。

  • 中秋節和大豐收的關聯?
  • 日本,話劇《結婚》劇本?