天天看點

阿裡巴巴Java開發手冊一、程式設計規約 二、異常日志 三、單元測試 四、安全規約 五、MySQL資料庫 六、工程結構 七、設計規約

目錄

一、程式設計規約

(一) 命名風格

(二) 常量定義

(三) 代碼格式

(四) OOP規約

(五) 集合處理

(六) 并發處理

(七) 控制語句

(八) 注釋規約

(九) 其它

二、異常日志

(一) 異常處理

(二) 日志規約

三、單元測試

四、安全規約

五、MySQL資料庫

(一) 建表規約

(二) 索引規約

(三) SQL語句

(四) ORM映射

六、工程結構

(一) 應用分層

(二) 二方庫依賴

(三) 伺服器

七、設計規約

https://blog.csdn.net/maomaotracy/article/details/80786610

https://www.cnblogs.com/carryjack/p/8450437.html

針對《阿裡巴巴Java開發手冊1.4》,将其中重要的整理如下:

一、程式設計規約

(一) 命名風格

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

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

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

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

        反例:定義為基本資料類型Boolean isDeleted的屬性,它的方法也是isDeleted(),RPC架構在反向解析的時候,“誤以為”對應的屬性名稱是deleted,導緻屬性擷取不到,進而抛出異常。

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

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

    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接口。

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

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

            1) 擷取單個對象的方法用get做字首。 

            2) 擷取多個對象的方法用list做字首,複數形式結尾如:listObjects。 

            3) 擷取統計值的方法用count做字首。 

            4) 插入的方法用save/insert做字首。 

            5) 删除的方法用remove/delete做字首。 

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

        B) 領域模型命名規約 

            1) 資料對象:xxxDO,xxx即為資料表名。 

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

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

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

(二) 常量定義

    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定義。

(三) 代碼格式

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

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

            }

        }

(四) OOP規約

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

    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(),但是這個屬性在資料提取時并沒有置入具體值,在更新其它字段時又附帶更新了此字段,導緻建立時間被修改成目前時間。

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

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

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

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

(五) 集合處理

    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>。

    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值組合集合。

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

(六) 并發處理

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

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

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

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

        1)FixedThreadPool 和 SingleThreadPoolPool: 允許的請求隊列長度為 Integer.MAX_VALUE,可 能會堆積大量的請求,進而導緻 OOM。 

        2)CachedThreadPool 和 ScheduledThreadPool : 允許的建立線程數量為 Integer.MAX_VALUE,可能會建立大量的線程,進而導緻 OOM。

    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之前,需要編碼保證每個線程持有一個執行個體。

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

        如果是count++操作,使用如下類實作:

            AtomicInteger count = new AtomicInteger(); 

            count.addAndGet(1); 

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

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

(七) 控制語句

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

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

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

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

        1) 調用頻次低的方法。 

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

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

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

        5) 敏感權限入口。

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

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

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

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

(八) 注釋規約

(九) 其它

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

二、異常日志

(一) 異常處理

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

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

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

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

    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問題。

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

        說明:關于RPC方法傳回方式使用Result方式的理由: 

            1)使用抛異常傳回方式,調用方如果沒有捕獲到就會産生運作時錯誤。 

            2)如果不加棧資訊,隻是new自定義異常,加入自己的了解的error message,對于調用端解決問題的幫助不會太多。如果加了棧資訊,在頻繁調用出錯的情況下,資料序列化和傳輸的性能損耗也是問題。

(二) 日志規約

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

        import org.slf4j.Logger;

        import org.slf4j.LoggerFactory;

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

    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級别隻記錄系統邏輯出錯、異常或者重要的錯誤資訊

三、單元測試

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

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

        . 存在過多的全局變量和靜态方法。 

        . 存在過多的外部依賴。 

        . 存在過多的條件語句。 

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

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

        . 那是測試同學幹的事情。本文開發 手冊 ,凡是本文内容都與開發同學強相關的。  

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

        . 單元測試代碼不需要維護。 一年半載後,那麼幾乎處于廢棄狀态 

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

四、安全規約

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

        說明:防止沒有做水準權限校驗就可随意通路、修改、删除别人的資料,比如檢視他人的私信内容、修改他人的訂單。

    2. 【強制】使用者敏感資料禁止直接展示,必須對展示資料進行脫敏。 

        說明:中國大陸個人手機号碼顯示為:158****9119,隐藏中間4位,防止隐私洩露。

    3. 【強制】使用者輸入的SQL參數嚴格使用參數綁定或者METADATA字段值限定,防止SQL注入,禁止字元串拼接SQL通路資料庫。

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

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

        對于存在CSRF漏洞的應用/網站,攻擊者可以事先構造好URL,隻要受害者使用者一通路,背景便在使用者不知情的情況下對資料庫中使用者參數進行相應修改。

五、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

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

        說明:如果預計三年後的資料量根本達不到這個級别,請不要在建立表時就分庫分表。

(二) 索引規約

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

        說明:不要以為唯一索引影響了insert速度,這個速度損耗可以忽略,但提高查找速度是明顯的;

        另外,即使在應用層做了非常完善的校驗控制,隻要沒有唯一索引,根據墨菲定律,必然有髒資料産生。

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

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

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

        說明:索引檔案具有B-Tree的最左字首比對特性,如果左邊的值未确定,那麼無法使用此索引。

    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

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

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

        說明:存在非等号和等号混合時,在建索引時,請把等号條件的列前置。如:where c>? and d=? 那麼即使c的區分度更高,也必須把d放在索引的最前列,即索引idx_d_c。

(三) 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。

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

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

(四) ORM映射

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

        說明:

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

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

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

    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”,會置入字段名和屬性值,但是值的類型不可控。

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

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

六、工程結構

(一) 應用分層

    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

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

        說明:<dependencyManagement>裡隻是聲明版本,并不實作引入,是以子項目需要顯式的聲明依賴,version和scope都讀取自父pom。而<dependencies>所有聲明在主pom的<dependencies>裡的依賴都會自動引入,并預設被所有的子項目繼承。

(三) 伺服器

    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 後調整堆大小帶來的壓力。

七、設計規約

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

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

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

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

10.【推薦】系統設計時,根據依賴倒置原則,盡量依賴抽象類與接口,有利于擴充與維護。 

    說明:低層次子產品依賴于高層次子產品的抽象,友善系統間的解耦。

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

    說明:極端情況下,傳遞的代碼都是不可修改的,同一業務域内的需求變化,通過子產品或類的擴充來實作。

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

    說明:随着代碼的重複次數不斷增加,維護成本指數級上升。

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

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

    反例:某團隊為了業務快速發展,靈活成了産品經理催進度的借口,系統中均是勉強能運作但像面條一樣的代碼,可維護性和可擴充性極差,一年之後,不得不進行大規模重構,得不償失。

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

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

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

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

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

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

    . 确定系統内子產品之間的關系。确定子產品之間的依賴關系及子產品的宏觀輸入和輸出。

    . 确定指導後續設計與演化的原則。使後續的子系統或子產品設計在規定的架構内繼續演化。

    . 确定非功能性需求。非功能性需求是指安全性、可用性、可擴充性。