記一次Alibaba代碼質量檢測問題歸總

前段時間針對公司老項目做了一次代碼規範質量檢查,採用的是阿里巴巴代碼檢測插件進行檢測,檢查結果並不理想,老項目比較重,開發迭代人員比較多,本文就是對這些檢查點做一個記錄,方便之後回顧。

Alibaba代碼檢測插件是2017年開發的,無論是IDEA還是Android Studio都可以安裝使用,深受開發者好評。androidstudio可以直接搜索插件Alibaba Java Coding Guidelines進行安裝使用。

Blocker

  1. if、for語句必須採用閉包形式,不允許採用無括號形勢編寫。

  2. 在使用正則表達式時,利用好其預編譯功能,可以有效加快編譯速度。

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

    public class XxxClass {
            // Use precompile
            private static Pattern NUMBER_PATTERN = Pattern.compile("[0-9]+");
            public Pattern getNumberPattern() {
                // Avoid use Pattern.compile in method body.
                Pattern localPattern = Pattern.compile("[0-9]+");
                return localPattern;
            }
        }
    
  3. 線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。

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

    a. FixedThreadPool和SingleThreadPool:
    允許的請求隊列長度爲Integer.MAX_VALUE,可能會堆積大量的請求,從而導致OOM。
    b. CachedThreadPool:
    允許的創建線程數量爲Integer.MAX_VALUE,可能會創建大量的線程,從而導致OOM。
    

    個人開發建議,全局封裝一個ThreadPoolExecutor線程池工具類,統一管理線程池申請。

Critical

  1. Map/Set的key爲自定義對象時,必須重寫hashCode和equals。

    關於hashCode和equals的處理,遵循如下規則:

    a. 只要重寫equals,就必須重寫hashCode。
    b. 因爲Set存儲的是不重複的對象,依據hashCode和equals進行判斷,所以Set存儲的對象必須重寫這兩個方法。 
    c. 如果自定義對象做爲Map的鍵,那麼必須重寫hashCode和equals。
    
  2. Object的equals方法容易拋空指針異常,應使用常量或確定有值的對象來調用equals。

  3. 不能使用過時的類或方法。

  4. 創建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。創建線程池的時候請使用帶ThreadFactory的構造函數,並且提供自定義ThreadFactory實現或者使用第三方實現。

    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
        .setNameFormat("demo-pool-%d").build();
    ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1,
        0L, TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
    
    singleThreadPool.execute(()-> System.out.println(Thread.currentThread().getName()));
    singleThreadPool.shutdown();
    
        
            
    public class TimerTaskThread extends Thread {
        public TimerTaskThread(){
        super.setName("TimerTaskThread");}
    

    個人開發建議,全局封裝一個ThreadPoolExecutor線程池工具類,暴露初始化線程名稱定義。

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

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

  7. 異常類命名使用Exception結尾。

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

  9. 所有編程相關的命名均不能以下劃線或者美元符號命名。

  10. 抽象類命名使用Abstract或Base開頭,抽象類一般需要被繼承使用的。

  11. 方法名、參數名、成員變量、局部變量都統一使用lowerCamelCase,必須遵從小駝峯形式

  12. 浮點數之間的等值判斷,基本數據類型不能用==來比較,包裝數據類型不能用equals來判斷。 浮點數採用“尾數+階碼”的編碼方式,類似於科學計數法的“有效數字+指數”的表示方式。二進制無法精確表示大部分的十進制小數,具體原理參考《碼出高效》。
    改進方式:

        1)指定一個誤差範圍,兩個浮點數的差值在此範圍之內,則認爲是相等的
            float a = 1.0f - 0.9f;
            float b = 0.9f - 0.8f;
            float diff = 1e-6f;
        
            if (Math.abs(a - b) < diff) {
                System.out.println("true");
            }
        2) 使用BigDecimal來定義值,再進行浮點數的運算操作
            BigDecimal a = new BigDecimal("1.0");
            BigDecimal b = new BigDecimal("0.9");
            BigDecimal c = new BigDecimal("0.8");
        
            BigDecimal x = a.subtract(b);
            BigDecimal y = b.subtract(c);
        
            if (x.equals(y)) {
                System.out.println("true");
            }
                    
        Negative example:
                float g = 0.7f-0.6f;
                float h = 0.8f-0.7f;
                if (g == h) {
                    System.out.println("true");
                }
         
         
         
        Positive example:
                double dis = 1e-6;
                double d1 = 0.0000001d;
                double d2 = 0d;
                System.out.println(Math.abs(d1 - d2) < dis);
    
    
  13. 線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。

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

Major

  1. 不允許任何魔法值(即未經定義的常量)直接出現在代碼中。
    Negative example:
        //Magic values, except for predefined, are forbidden in coding.
        if (key.equals("Id#taobao_1")) {
                //...
        }
             
            
            
    Positive example:
        String KEY_PRE = "Id#taobao_1";
        if (KEY_PRE.equals(key)) {
                //...
        }
    
  2. 中括號是數組類型的一部分,數組定義如下:String[] args
Negative example:
        String extArrayString[] = { ".amr", ".ogg", ".mp3", ".aac", ".ape",
				".flac", ".wma", ".wav", ".mp2", ".mid", ".3gpp" };
  1. 包名統一使用小寫,點分隔符之間有且僅有一個自然語義的英語單詞。包名統一使用單數形式,但是類名如果有複數含義,類名可以使用複數形式。

    個人建議,如果遇到包名必須用兩個或以上的單詞才能表達,可以採用Spring的命名方式,兩個單詞用點分割;開發過程中我們儘量採用一個單詞進行表達。

  2. 單個方法的總行數不超過80行。

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

  3. 及時清理不再使用的代碼段或配置信息。

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

  4. 循環體內,字符串的聯接方式,使用StringBuilder的append方法進行擴展。

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

    Negative example:
            String result;
            for (String string : tagNameList) {
                result = result + string;
            }
    
        
            
    Positive example:
            StringBuilder stringBuilder = new StringBuilder();
            for (String string : tagNameList) {
                stringBuilder.append(string);
            }
            String result = stringBuilder.toString();
    
  5. 所有的抽象方法(包括接口中的方法)必須要用javadoc註釋、除了返回值、參數、異常說明外,還必須指出該方法做什麼事情,實現什麼功能。

    說明:如有實現和調用注意事項,請一併說明。

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

        public void method() {
            // Put single line comment above code. (Note: align '//' comment with code)
            int a = 3;
        
            /**
            * Some description about follow code. (Note: align '/**' comment with code)
            */
            int b = 4;
        }
    
  7. 類、類屬性、類方法的註釋必須使用javadoc規範,使用/*內容/格式,不得使用//xxx方式和/xxx/方式。 說明:在IDE編輯窗口中,javadoc方式會提示相關注釋,生成javadoc可以正確輸出相應註釋;在IDE中,工程調用方法時,不進入方法即可懸浮提示方法、參數、返回值的意義,提高閱讀效率。

        /**
         * 
         * XXX class function description.
         *
         */
        public class XxClass implements Serializable {
            private static final long serialVersionUID = 113323427779853001L;
            /**
             * id
             */
            private Long id;
            /**
             * title
             */
            private String title;
        
            /**
             * find by id
             * 
             * @param ruleId rule id
             * @param page start from 1
             * @return Result<Xxxx>
             */
            public Result<Xxxx> funcA(Long ruleId, Integer page) {
                return null;
            }
        }
    
  8. 類名使用UpperCamelCase風格,必須遵從駝峯形式,但以下情形例外:(領域模型的相關命名)DO / BO / DTO / VO / DAO

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

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

    
    Negative example:
            if ((file.open(fileName, "w") != null) && (...) || (...)) {
                // ...
            }
    		
    		
    		
    Positive example:
            boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
            if (existed) {
                //...
            }
    
  10. 集合初始化時,指定集合初始值大小。

    說明:HashMap使用如下構造方法進行初始化,如果暫時無法確定集合大小,那麼指定默認值(16)即可。

     Negative example:   
       Map<String, String> map = new HashMap<String, String>();
        
        
        
     Positive example: 
       Map<String, String> map = new HashMap<String, String>(16);
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章