1. 程式人生 > >代碼規範實踐

代碼規範實踐

time keyword 權限 gettime 參數錯誤 完整 類成員 構造 type

規範等級說明

  • 級別I: 默認級別,要求所有項目必須遵守。
  • 級別II: 建議所有項目中遵守。
  • 級別III: 鼓勵各個項目根據實際情況執行。

1.格式與命名規範(Formating and Naming Conventions)

1.1 縮進

使用Tab縮進。(I)

1.2 換行

每行80字符。(I)

if,for,while語句只有單句時,如果該句可能引起閱讀混淆,需要用" {"和"}"括起來,否則可以省略。

//錯誤,需要使用花括號{}括起來
if (condition)
if(condition) doSomething();
else
doSomething();

1.3 命名規則

  • 不允許使用漢語拼音命名 (I)
  • 代碼命名不能以下劃線或美元符號開始或結束,反例: _name / __name / $Object / name_ / name$ / Object$ (I)
  • 命令必須用駝峰形式 (I)
  • 常量命名全部大寫,單詞間用下劃線隔開,力求語義表達完整清楚,不要嫌名字長;(I)

  • 遇到縮寫如XML時,僅首字母大寫,即loadXmlDocument()而不是loadXMLDocument()
  • Package名必須全部小寫,盡量使用單個單詞;(I)
  • Interface名可以是一個名詞或形容詞(加上‘able‘,‘ible‘, or ‘er‘後綴),如Runnable,Accessible。
    為了基於接口編程,不采用首字母為I或加上IF後綴的命名方式,如IBookDao,BookDaoIF。
  • 頁面部件名建議命名為:btnOK、lblName或okBtn、nameLbl。(II)
    其中btn、lbl縮寫代表按鈕(Button)、標簽(Label)。
  • 局部變量及輸入參數不要與類成員變量同名(get/set方法與構造函數除外)
  • 抽象類命名使用 Abstract 或 Base 開頭;異常類命名使用 Exception 結尾;測試類 命名以它要測試的類的名稱開始,以 Test 結尾 (I)
  • 枚舉類名建議帶上 Enum 後綴,枚舉成員名稱需要全大寫,單詞間用下劃線隔開;(I)

  • 各層命名規約:
    A) Service/DAO層方法命名規約 (I)

    1) 獲取單個對象的方法用findBy做前綴。(I)
    2) 獲取多個對象的方法用list做前綴,
    3) 如果是查詢用search做前綴。
    4) 獲取統計值的方法用count做前綴。
    5) 插入的方法用save(推薦)或insert做前綴。
    6) 刪除的方法用remove(推薦)或delete做前綴。
    7) 修改的方法用update做前綴。

    B) 領域模型命名規約 (I)
    1) 數據對象:xxx,遵循駝峰,xxx即為數據表名,例如 User。
    2) 數據傳輸對象:xxxDTO,xxx為業務領域相關的名稱。
    3) 展示對象:xxxVO,xxx一般為網頁名稱。
    4) POJO是DO/DTO/BO/VO的統稱,禁止命名成xxxPOJO。

  • 常量定義

    • 常量必須先定義(I)

1.4 聲明

  • 修飾符應該按照如下順序排列:public, protected, private, abstract, static, final, transient, volatile, synchronized, native, strictfp。
  • 類與接口的聲明順序(可用Eclipse的source->sort members功能自動排列):
    1. 靜態成員變量 / Static Fields
    2. 靜態初始化塊 / Static Initializers
    3. 成員變量 / Fields
    4. 初始化塊 / Initializers
    5. 構造器 / Constructors
    6. 靜態成員方法 / Static Methods
    7. 成員方法 / Methods
    8. 重載自Object的方法如toString(), hashCode() 和main方法
    9. 類型(內部類) / Types(Inner Classes)

同等的類型,按public, protected, private的順序排列。

1.5 格式

  • IDE的text file encoding設置為UTF-8; IDE中文件的換行符使用Unix格式, 不要使用 windows 格式 (I)

2.註釋規範(Document Convertions)

2.1 註釋類型

2.1.1 JavaDoc註釋

略。

2.1.2 失效代碼註釋

由/*...*/界定,標準的C-Style的註釋。專用於註釋已失效的代碼。

/*
 * Comment out the code
 * String s = "hello";
 * System.out.println(s);
 */

2.1.3 代碼細節註釋

由//界定,專用於註釋代碼細節,即使有多行註釋也仍然使用//,以便與用/**/註釋的失效代碼分開

除了私有變量外,不推薦使用行末註釋。

class MyClass {
    private int myField; // An end-line comment.

    public void myMethod {
        //a very very long
       //comment.
       if (condition1) {
       //condition1 comment
          ...
        } else {
        //elses condition comment
          ...
        }
    }
}          

2.2 註釋的格式

  • 註釋中的第一個句子要以(英文)句號、問號或者感嘆號結束。Javadoc生成工具會將註釋中的第一個句子放在方法匯總表和索引中。
  • 為了在JavaDoc和IDE中能快速鏈接跳轉到相關聯的類與方法,盡量多的使用@see xxx.MyClass,@see xx.MyClass#find(String)。
  • Class必須以@author 作者名聲明作者,不需要聲明@version與@date,由版本管理系統保留此信息。(II)
  • 如果註釋中有超過一個段落,用<p>分隔。(II)
  • 示例代碼以<pre></pre>包裹。(II)
  • 標識(java keyword, class/method/field/argument名,Constants) 以<code></code>包裹。(II)
  • 標識在第一次出現時以{@linkxxx.Myclass}註解以便JavaDoc與IDE中可以鏈接。(II)

2.3 註釋的內容

2.3.1 可精簡的註釋內容

註釋中的每一個單詞都要有其不可缺少的意義,註釋裏不寫"@param name -名字"這樣的廢話。
如果該註釋是廢話,連同標簽刪掉它,而不是自動生成一堆空的標簽,如空的@param name,空的@return。

2.3.2 推薦的註釋內容

  • 對於API函數如果存在契約,必須寫明它的前置條件(precondition),後置條件(postcondition),及不變式(invariant)。(II)
  • 對於調用復雜的API盡量提供代碼示例。(II)
  • 對於已知的Bug需要聲明。(II)
  • 在本函數中拋出的unchecked exception盡量用@throws說明。(II)
  • 類、類屬性、類方法的註釋必須使用 Javadoc 規範,使用/**內容*/格式,不得使用 //xxx 方式
  • 所有的抽象方法(包括接口中的方法)必須要用 Javadoc 註釋、除了返回值、參數、 異常說明外,還必須指出該方法做什麽事情,實現什麽功能
  • 所有的類都必須添加創建者信息 方法內部單行註釋,在被註釋語句上方另起一行,使用//註釋。方法內部多行註釋使用/* */註釋,註意與代碼對齊
  • 所有的枚舉類型字段必須要有註釋,說明每個數據項的用途 代碼修改的同時,註釋也要進行相應的修改,尤其是參數、返回值、異常、核心邏輯 等的修改

2.3.3 Null規約

如果方法允許Null作為參數,或者允許返回值為Null,必須在JavaDoc中說明。
如果沒有說明,方法的調用者不允許使用Null作為參數,並認為返回值是Null Safe的。

/**
 * 獲取對象.
 *
 * @ return the object to found or null if not found.
 */
Object get(Integer id){
...
}

2.3.4 特殊代碼註釋

  • 代碼質量不好但能正常運行,或者還沒有實現的代碼用//TODO: 或 //XXX:聲明
  • 存在錯誤隱患的代碼用//FIXME:聲明

3.編程規範(Programming Conventions)

3.1基本規範

  1. 當面對不可知的調用者時,方法需要對輸入參數進行校驗,如不符合拋出IllegalArgumentException,建議使用Spring的Assert系列函數。 (II)
  2. 隱藏工具類的構造器,確保只有static方法和變量的類不能被構造實例。
  3. 變量,參數和返回值定義盡量基於接口而不是具體實現類,如Map map = new HashMap();Map map = Maps.newHashMap();
  4. 代碼中不能使用System.out.println(),e.printStackTrace(),必須使用logger打印信息。(I)

3.2 異常處理

  1. 重新拋出的異常必須保留原來的異常,即throw new NewException("message", e); 而不能寫成throw new NewException("message")。(I)
  2. 在所有異常被捕獲且沒有重新拋出的地方必須寫日誌。 (I)
  3. 如果屬於正常異常的空異常處理塊必須註釋說明原因,否則不允許空的catch塊。(I)
  4. 框架盡量捕獲低級異常,並封裝成高級異常重新拋出,隱藏低級異常的細節。(III)
  5. 不要捕獲 Java 類庫中定義的繼承自 RuntimeException 的運行時異常類,如: IndexOutOfBoundsException / NullPointerException,這類異常由程序員預檢查 來規避,保證程序健壯性
  6. 異常不要用來做流程控制,條件控制,因為異常的處理效率比條件分支低 捕獲異常是為了處理它,不要捕獲了卻什麽都不處理而拋棄之,如果不想處理它,請 將該異常拋給它的調用者。最外層的業務使用者,必須處理異常,將其轉化為用戶可以理解的 內容
  7. 有 try 塊放到了事務代碼中,catch 異常後,如果需要回滾事務,一定要註意手動回 滾事務
  8. finally 塊必須對資源對象、流對象進行關閉,有異常也要做 try-catch
  9. 不能在 finally 塊中使用 return,finally 塊中的 return 返回後方法結束執行,不 會再執行 try 塊中的 return 語句
  10. 方法的返回值可以為 null,不強制返回空集合,或者空對象等,必須添加註釋充分 說明什麽情況下會返回 null 值。調用方需要進行 null 判斷防止 NPE 問題 (I)
  11. 防止 NPE,是程序員的基本修養,註意 NPE 產生的場景:
    1) 返回類型為包裝數據類型,有可能是null,返回int值時註意判空。

    反例:public int f(){ return Integer 對象}; 如果為 null,自動解箱拋 NPE。
    2) 數據庫的查詢結果可能為null。
    3) 集合裏的元素即使isNotEmpty,取出的數據元素也可能為null。
    4) 遠程調用返回對象,一律要求進行NPE判斷。

    5) 對於Session中獲取的數據,建議NPE檢查,避免空指針。
    6) 級聯調用obj.getA().getB().getC();一連串調用,易產生NPE。

3.3 JDK

  1. 重載方法必須使用@Override,可避免父類方法改變時導致重載函數失效。
  2. 不需要關心的warning信息用@SuppressWarnings("unused"), @SuppressWarnings("unchecked"), @SuppressWarnings("serial") 註釋。

3.4 日誌

  1. 應用中不可直接使用日誌系統(Log4j、Logback)中的 API,而應依賴使用日誌框架(I)

    SLF4J 中的 API,使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式統一。

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

  2. 對 trace/debug/info 級別的日誌輸出,必須使用條件輸出形式或者使用占位符的方式,(I)

    logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);

  3. 異常信息應該包括兩類信息:案發現場信息和異常堆棧信息。如果不處理,那麽往上 拋。正例:logger.error(各類參數或者對象toString + "_" + e.getMessage(), e); (I)

4.OOP規範

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

  • 所有的覆寫方法,必須加@Override 註解(I)

  • 不能使用過時的類或方法

  • Object 的 equals 方法容易拋空指針異常,應使用常量或確定有值的對象來調用 equals ,正例: "test".equals(object); 反例: object.equals("test"); (I) 說明:推薦使用java.util.Objects#equals (JDK7引入的工具類)

  • 所有的相同類型的包裝類對象之間值的比較,全部使用 equals 方法比較 (I)

  • 定義 DO/DTO/VO 等 POJO 類時,不要設定任何屬性默認值

  • 構造方法裏面禁止加入任何業務邏輯,如果有初始化邏輯,請放在 init 方法中 (I)

  • POJO 類必須寫 toString 方法。使用 IDE 的中工具:右鍵> generate->toString 時,如果繼承了另一個 POJO 類,註意在前面加一下 super.toString。 說明:在方法執行拋出異常時,可以直接調用 POJO 的 toString()方法打印其屬性值,便於排 查問題。修改默認模板方法 (I)

  • 當一個類有多個構造方法,或者多個同名方法,這些方法應該按順序放置在一起, 便於閱讀

  • 類內方法定義順序依次是:公有方法或保護方法 > 私有方法 > getter/setter方法

  • 字符串的聯接方式,使用 StringBuilder 的 append 方法進行擴展 (I)

  • final 可提高程序響應效率,聲明成 final 的情況 ,
    a>不需要重新賦值的變量,包括類屬性、局部變量
    b>對象參數前加final,表示不允許修改引用的指向
    c>類方法確定不允許被重寫

  • 在 if/else/for/while/do 語句中必須使用大括號,即使只有一行代碼,避免使用 下面的形式:if (condition) statements;

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

  • 接口入參保護,這種場景常見的是用於做批量操作的接口

  • 方法中需要進行參數校驗的場景,

    1. 調用頻次低的方法

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

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

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

    5. 敏感權限入口

  • 方法中不需要參數校驗的場景

    1. 極有可能被循環調用的方法,不建議對參數進行校驗。但在方法說明裏必須註明外部參數檢查

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

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

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

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

  • 獲取當前毫秒數 System.currentTimeMillis(); 而不是 new Date().getTime() ,

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

  • 任何數據結構的構造或初始化,都應指定大小,避免數據結構無限增長吃光內存

5.集合處理
  • 不要在 foreach 循環裏進行元素的 remove/add 操作。remove 元素請使用 Iterator方式,如果並發操作,需要對 Iterator 對象加鎖 (I)

    Iterator<String> it = a.iterator(); 
    while(it.hasNext()){
           String temp = it.next(); 
           if(刪除元素的條件){
               it.remove(); 
           } 
    } 

  • 集合初始化時,盡量指定集合初始值大小,說明:ArrayList盡量使用ArrayList(int initialCapacity) 初始化

  • 使用 entrySet 遍歷 Map 類集合 KV,而不是 keySet 方式進行遍歷

  • 利用 Set 元素唯一的特性,可以快速對一個集合進行去重操作,避免使用 List 的 contains 方法進行遍歷、對比、去重操作

6.並發處理
  • 獲取單例對象需要保證線程安全,其中的方法也要保證線程安全,資源驅動類、工具類、單例工廠類都需要註意

  • 創建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯

  • 線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程

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

  • 【強制】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 代替 Simpledateformatter,官方給出的解釋:simple beautiful strong immutable thread-safe。

  • 並發修改同一記錄時,避免更新丟失,要麽在應用層加鎖,要麽在緩存加鎖,要麽在 數據庫層使用樂觀鎖,使用 version 作為更新依據。說明:如果每次訪問沖突概率小於 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次 數不得小於 3 次。

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

5.自動代碼檢查

使用Eclipse技術分享圖片Inellij IDEA技術分享圖片的代碼校驗功能已經排除了很多問題。

  1. Eclipse:在Windows->Preferences->Java-Compiler->Errors/Warnings中,按本文檔將一些原來Ignore的規則打開。
  2. IDEA:在Setting->Errors中設定規則,調用Analyzer->Inspece Code進行校驗。

6.其他

uap關於list的遍歷

List result = biz.getList();
if(result!=null){
    for(int i=0;i<CheckList.checkListSize(result);i++){
        // TODO
    }
}
addWhere("param=123");
改成
addWhere("param","=","123");

代碼規範實踐