【技術積累】軟體開發規範【一】

2023-07-23 18:00:20

程式設計規約

命名風格

  • 【強制】程式碼中的命名均不能以下劃線或美元符號開始,也不能以下劃線或美元符號結束
  • 【強制】程式碼中的命名嚴禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。說明:正確的英文拼寫和語法可以讓閱讀者易於理解,避免歧義。注意,即使純拼音命名方式也要避免採用。
  • 【強制】類名使用 UpperCamelCase 風格,必須遵從駝峰形式,但以下情形例外: DO / BO /DTO / VO / AO
  • 【強制】方法名、引數名、成員變數、區域性變數都統一使用 lowerCamelCase 風格,必須遵從駝峰形式。
  • 【強制】常數命名全部大寫,單詞間用下劃線隔開,力求語意表達完整清楚,不要嫌名字長。
  • 【強制】抽象類命名使用 Abstract 或 Base 開頭 ; 異常類命名使用 Exception 結尾 ; 測試類命名以它要測試的類的名稱開始,以 Test 結尾。
  • 【強制】中括號是陣列型別的一部分,陣列定義如下: String[] args;
  • 【強制】 POJO 類中布林型別的變數,都不要加 is ,否則部分框架解析會引起序列化錯誤。定義為基本資料型別 Boolean isDeleted; 的屬性,它的方法也是 isDeleted() , RPC框架在反向解析的時候,「以為」對應的屬性名稱是 deleted ,導致屬性獲取不到,進而丟擲異常
  • 【強制】包名統一使用小寫,點分隔符之間有且僅有一個自然語意的英語單詞。包名統一使用單數形式,但是類名如果有複數含義,類名可以使用複數形式。
  • 【強制】杜絕完全不規範的縮寫,避免望文不知義。AbstractClass 「縮寫」命名成 AbsClass;condition 「縮寫」命名成 condi ,此類隨意縮寫嚴重降低了程式碼的可閱讀性。
  • 【推薦】為了達到程式碼自解釋的目標,任何自定義程式設計元素在命名時,使用盡量完整的單詞組合來表達其意。
  • 【推薦】如果模組、介面、類、方法使用了設計模式,在命名時體現出具體模式。說明:將設計模式體現在名字中,有利於閱讀者快速理解架構設計理念。public class OrderFactory;
  • 【推薦】介面類中的方法和屬性不要加任何修飾符號 (public 也不要加 ) ,保持程式碼的簡潔性,並加上有效的 Javadoc 註釋。儘量不要在介面裡定義變數,如果一定要定義變數,肯定是與介面方法相關,並且是整個應用的基礎常數。介面方法簽名: void f();介面基礎常數表示: String COMPANY = " alibaba " 說明: JDK 8 中介面允許有預設實現,那麼這個 default 方法,是對所有實現類都有價值的預設實現。
  • 【強制】對於 Service 和 DAO 類,基於 SOA 的理念,暴露出來的服務一定是介面,內部的實現類用 Impl 的字尾與介面區別。正例: CacheServiceImpl 實現 CacheService 介面。
  • 【推薦】 如果是形容能力的介面名稱,取對應的形容詞做介面名 (通常是–able的形式)。正例: AbstractTranslator 實現 Translatable 。
  • 【參考】列舉類名建議帶上 Enum 字尾,列舉元名稱需要全大寫,單詞間用下劃線隔開。說明:列舉其實就是特殊的常數類,且構造方法被預設強制是私有。
  • 【參考】各層命名規約:Service / DAO 層方法命名規約
    • 獲取單個物件的方法用 get 做字首。
    • 獲取多個物件的方法用 list 做字首。
    • 獲取統計值的方法用 count 做字首。
    • 插入的方法用 save/insert 做字首。
    • 刪除的方法用 remove/delete 做字首。
    • 修改的方法用 update 做字首。
  • 【參考】領域模型命名規約
    • 資料物件: xxxDO , xxx 即為資料表名。
    • 資料傳輸物件: xxxDTO , xxx 為業務領域相關的名稱。
    • 展示物件: xxxVO , xxx 一般為網頁名稱。
    • POJO 是 DO / DTO / BO / VO 的統稱,禁止命名成 xxxPOJO 。

常數定義

  • 【強制】不允許任何魔法值 ( 即未經定義的常數 ) 直接出現在程式碼中。
  • 【強制】 long 或者 Long 初始賦值時,使用大寫的 L ,不能是小寫的 l ,小寫容易跟數位 1 混淆,造成誤解。
  • 【推薦】不要使用一個常數類維護所有常數,按常數功能進行歸類,分開維護。說明:大而全的常數類,非得使用查詢功能才能定位到修改的常數,不利於理解和維護。
  • 【推薦】常數的複用層次有五層:跨應用共用常數、應用內共用常數、子工程內共用常數、包內共用常數、類內共用常數。
    • 跨應用共用常數:放置在二方庫中,通常是 client . jar 中的 constant 目錄下
    • 應用內共用常數:放置在一方庫中,通常是 modules 中的 constant 目錄下
    • 子工程內部共用常數:即在當前子工程的 constant 目錄下。
    • 包內共用常數:即在當前包下單獨的 constant 目錄下。
    • 類內共用常數:直接在類內部 private static final 定義。
  • 【推薦】如果變數值僅在一個範圍內變化,且帶有名稱之外的延伸屬性,定義為列舉類。下面正例中的數位就是延伸資訊,表示星期幾。正例: public Enum { MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6),SUNDAY(7);}

程式碼格式

  • 【強制】大括號的使用約定。如果是大括號內為空,則簡潔地寫成{}即可,不需要換行 ; 如果是非空程式碼塊則:
    • 左大括號前不換行。
    • 左大括號後換行。
    • 右大括號前換行。
    • 右大括號後還有 else 等程式碼則不換行 ; 表示終止的右大括號後必須換行。
  • 【強制】 左小括號和字元之間不出現空格 ; 同樣,右小括號和字元之間也不出現空格。
  • 【強制】 if / for / while / switch / do 等保留字與括號之間都必須加空格。
  • 【強制】任何二目、三目運運算元的左右兩邊都需要加一個空格。
  • 【強制】採用 4 個空格縮排,禁止使用 tab 字元。說明:如果使用 tab 縮排,必須設定 1 個 tab 為 4 個空格。IDEA 設定 tab 為 4 個空格時,請勿勾選 Use tab character ;而在 eclipse 中,必須勾選 insert spaces for tabs 。
  • 【強制】註釋的雙斜線與註釋內容之間有且僅有一個空格。正例: // 註釋內容,注意在 // 和註釋內容之間有一個空格。
    • 【強制】單行字元數限制不超過 120 個,超出需要換行,換行時遵循如下原則:
    • 第二行相對第一行縮排 4 個空格,從第三行開始,不再繼續縮排,參考範例。
    • 運運算元與下文一起換行。
    • 方法呼叫的點符號與下文一起換行。
    • 方法呼叫時,多個引數,需要換行時,在逗號後進行。
    • 在括號前不要換行
    • 【強制】方法引數在定義和傳入時,多個引數逗號後邊必須加空格。
  • 【強制】 IDE 的 text file encoding 設定為 UTF -8 ; IDE 中檔案的換行符使用 Unix 格式,不要使用 Windows 格式。
  • 【推薦】沒有必要增加若干空格來使某一行的字元與上一行對應位置的字元對齊。
  • 【推薦】方法體內的執行語句組、變數的定義語句組、不同的業務邏輯之間或者不同的語意之間插入一個空行。相同業務邏輯和語意之間不需要插入空行。

OOP規約

  • 【強制】避免通過一個類的物件參照存取此類的靜態變數或靜態方法,無謂增加編譯器解析成本,直接用類名來存取即可。
  • 【強制】所有的覆寫方法,必須加@ Override 註解。說明: getObject() 與 get 0 bject() 的問題。一個是字母的 O ,一個是數位的 0,加@ Override可以準確判斷是否覆蓋成功。另外,如果在抽象類中對方法簽名進行修改,其實現類會馬上編譯報錯。
  • 【強制】相同引數型別,相同業務含義,才可以使用 Java 的可變引數,避免使用 Object 。說明:可變引數必須放置在參數列的最後。
  • 【強制】外部正在呼叫或者二方庫依賴的介面,不允許修改方法簽名,避免對介面呼叫方產生影響。介面過時必須加@ Deprecated 註解,並清晰地說明採用的新介面或者新服務是什麼。
  • 【強制】不能使用過時的類或方法。說明: java . net . URLDecoder 中的方法 decode(String encodeStr) 這個方法已經過時,應該使用雙引數 decode(String source, String encode) 。介面提供方既然明確是過時介面,那麼有義務同時提供新的介面 ; 作為呼叫方來說,有義務去考證過時方法的新實現是什麼。
  • 【強制】 Object 的 equals 方法容易拋空指標異常,應使用常數或確定有值的物件來呼叫equals 。正例:" test " .equals(object);
  • 【強制】所有的相同型別的包裝類物件之間值的比較,全部使用 equals 方法比較。
  • 【強制】所有的 POJO 類屬性必須使用包裝資料型別。
  • 【強制】 RPC 方法的返回值和引數必須使用包裝資料型別。
  • 【推薦】所有的區域性變數使用基本資料型別。POJO 類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何NPE 問題,或者入庫檢查,都由使用者來保證。正例:資料庫的查詢結果可能是 null ,因為自動拆箱,用基本資料型別接收有 NPE 風險。
  • 【強制】定義 DO / DTO / VO 等 POJO 類時,不要設定任何屬性預設值。
  • 【強制】序列化類新增屬性時,請不要修改 serialVersionUID 欄位,避免反序列失敗 ; 如果完全不相容升級,避免反序列化混亂,那麼請修改 serialVersionUID 值。說明:注意 serialVersionUID 不一致會丟擲序列化執行時異常。
  • 【強制】構造方法裡面禁止加入任何業務邏輯,如果有初始化邏輯,請放在 init 方法中。
  • 【強制】 POJO 類必須寫 toString 方法。使用 IDE 的中工具: source > generate toString時,如果繼承了另一個 POJO 類,注意在前面加一下 super.toString 。說明:在方法執行丟擲異常時,可以直接呼叫 POJO 的 toString() 方法列印其屬性值,便於排查問題。
  • 【推薦】當一個類有多個構造方法,或者多個同名方法,這些方法應該按順序放置在一起,便於閱讀
  • 【推薦】 類內方法定義順序依次是:公有方法或保護方法 > 私有方法 > getter / setter方法。【上一條方法優先】
  • 【推薦】 setter 方法中,引數名稱與類成員變數名稱一致, this .成員名 = 引數名。在getter / setter 方法中,不要增加業務邏輯,增加排查問題的難度。
  • 【推薦】迴圈體內,字串的連線方式,使用 StringBuilder 的 append 方法進行擴充套件。說明:反編譯出的位元組碼檔案顯示每次迴圈都會 new 出一個 StringBuilder 物件,然後進行append 操作,最後通過 toString 方法返回 String 物件,造成記憶體資源浪費。
  • 【推薦】 final 可以宣告類、成員變數、方法、以及本地變數,下列情況使用 final 關鍵字
    • 不允許被繼承的類,如: String 類。
    • 不允許修改參照的域物件,如: POJO 類的域變數。
    • 不允許被重寫的方法,如: POJO 類的 setter 方法。
    • 不允許執行過程中重新賦值的區域性變數。
    • 避免上下文重複使用一個變數,使用 final 描述可以強制重新定義一個變數,方便更好地進行重構。
  • 【推薦】慎用 Object 的 clone 方法來拷貝物件。說明:物件的 clone 方法預設是淺拷貝,若想實現深拷貝需要重寫 clone 方法實現屬性物件的拷貝。
  • 【推薦】類成員與方法存取控制從嚴:
    • 如果不允許外部直接通過 new 來建立物件,那麼構造方法必須是 private 。
    • 工具類不允許有 public 或 default 構造方法。
    • 類非 static 成員變數並且與子類共用,必須是 protected 。
    • 類非 static 成員變數並且僅在本類使用,必須是 private 。
    • 類 static 成員變數如果僅在本類使用,必須是 private 。
    • 若是 static 成員變數,必須考慮是否為 final 。
    • 類成員方法只供類內部呼叫,必須是 private 。
    • 類成員方法只對繼承類公開,那麼限制為 protected 。

集合處理

  • 【強制】關於 hashCode 和 equals 的處理,遵循如下規則:
    • 只要重寫 equals ,就必須重寫 hashCode 。
    • 因為 Set 儲存的是不重複的物件,依據 hashCode 和 equals 進行判斷,所以 Set 儲存的物件必須重寫這兩個方法。
    • 如果自定義物件做為 Map 的鍵,那麼必須重寫 hashCode 和 equals 。
    • String 重寫了 hashCode 和 equals 方法,所以我們可以非常愉快地使用 String 物件作為 key 來使用。
  • 【強制】 ArrayList 的 subList 結果不可強轉成 ArrayList ,否則會丟擲 ClassCastException異常,即 java . util . RandomAccessSubList cannot be cast to java . util . ArrayList .說明: subList 返回的是 ArrayList 的內部類 SubList ,並不是 ArrayList ,而是ArrayList 的一個檢視,對於 SubList 子列表的所有操作最終會反映到原列表上。
  • 【強制】在 subList 場景中,高度注意對原集合元素個數的修改,會導致子列表的遍歷、增加、刪除均會產生 ConcurrentModificationException 異常。
  • 【強制】使用集合轉陣列的方法,必須使用集合的 toArray(T[] array) ,傳入的是型別完全一樣的陣列,大小就是 list . size() 。說明:使用 toArray 帶參方法,入參分配的陣列空間不夠大時, toArray 方法內部將重新分配記憶體空間,並返回新陣列地址 ; 如果陣列元素大於實際所需,下標為 [ list . size() ] 的陣列元素將被置為 null ,其它陣列元素保持原值,因此最好將方法入引陣列大小定義與集合元素個數一致。
  • 【強制】使用工具類 Arrays . asList() 把陣列轉換成集合時,不能使用其修改集合相關的方法,它的 add / remove / clear 方法會丟擲 UnsupportedOperationException 異常。說明: asList 的返回物件是一個 Arrays 內部類,並沒有實現集合的修改方法。 Arrays . asList體現的是介面卡模式,只是轉換介面,後臺的資料仍是陣列。
  • 【強制】泛型萬用字元<? extends T >來接收返回的資料,此寫法的泛型集合不能使用 add 方法,而 <? super T> 不能使用 get 方法,做為介面呼叫賦值時易出錯。
    • 第一、頻繁往外讀取內容的,適合用<? extends T >。
    • 第二、經常往裡插入的,適合用 <? super T> 。
  • 【強制】不要在 foreach 迴圈裡進行元素的 remove / add 操作。 remove 元素請使用 Iterator方式,如果並行操作,需要對 Iterator 物件加鎖。
  • 【強制】 在 JDK 7 版本及以上, Comparator 要滿足如下三個條件,不然 Arrays . sort ,Collections . sort 會報 IllegalArgumentException 異常。
    • x , y 的比較結果和 y , x 的比較結果相反。
    • x > y , y > z ,則 x > z
    • x = y ,則 x , z 比較結果和 y , z 比較結果相同。
  • 【推薦】集合初始化時,指定集合初始值大小。 HashMap 使用 HashMap(int initialCapacity) 初始化,
  • 【推薦】使用 entrySet 遍歷 Map 類集合 KV ,而不是 keySet 方式進行遍歷。說明: keySet 其實是遍歷了 2 次,一次是轉為 Iterator 物件,另一次是從 hashMap 中取出key 所對應的 value 。而 entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK 8,使用 Map . foreach 方法。
  • 【推薦】高度注意 Map 類集合 K / V 能不能儲存 null 值的情況
  • 【參考】合理利用好集合的有序性 (sort) 和穩定性 (order) ,避免集合的無序性 (unsort) 和不穩定性 (unorder) 帶來的負面影響。說明:有序性是指遍歷的結果是按某種比較規則依次排列的。穩定性指集合每次遍歷的元素次序是一定的。如: ArrayList 是 order / unsort;HashMap 是 unorder / unsort;TreeSet 是order / sort 。
  • 【參考】利用 Set 元素唯一的特性,可以快速對一個集合進行去重操作,避免使用 List 的contains 方法進行遍歷、對比、去重操作。

並行處理

  • 【強制】獲取單例物件需要保證執行緒安全,其中的方法也要保證執行緒安全。說明:資源驅動類、工具類、單例工廠類都需要注意。
  • 【強制】建立執行緒或執行緒池時請指定有意義的執行緒名稱,方便出錯時回溯。
  • 【強制】執行緒資源必須通過執行緒池提供,不允許在應用中自行顯式建立執行緒。說明:使用執行緒池的好處是減少在建立和銷燬執行緒上所花的時間以及系統資源的開銷,解決資源不足的問題。如果不使用執行緒池,有可能造成系統建立大量同類執行緒而導致消耗完記憶體或者「過度切換」的問題。
  • 【強制】執行緒池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確執行緒池的執行規則,規避資源耗盡的風險。
    • FixedThreadPool 和 SingleThreadPool :允許的請求佇列長度為 Integer.MAX_VALUE ,可能會堆積大量的請求,從而導致 OOM 。
    • CachedThreadPool和ScheduledThreadPool :允許的建立執行緒數量為Integer.MAX_VALUE ,可能會建立大量的執行緒,從而導致 OOM
  • 【強制】 SimpleDateFormat 是執行緒不安全的類,一般不要定義為 static 變數,如果定義為static ,必須加鎖,或者使用 DateUtils 工具類。正例:注意執行緒安全,使用 DateUtils 。說明:如果是 JDK 8 的應用,可以使用 Instant 代替 Date , LocalDateTime 代替 Calendar ,DateTimeFormatter 代替 SimpleDateFormat ,官方給出的解釋: simple beautiful strong immutable thread - safe 。
  • 【強制】高並行時,同步呼叫應該去考量鎖的效能損耗。能用無鎖資料結構,就不要用鎖 ; 能鎖區塊,就不要鎖整個方法體 ; 能用物件鎖,就不要用類鎖。說明:儘可能使加鎖的程式碼塊工作量儘可能的小,避免在鎖程式碼塊中呼叫 RPC 方法。
  • 【強制】對多個資源、資料庫表、物件同時加鎖時,需要保持一致的加鎖順序,否則可能會造成死鎖。說明:執行緒一需要對錶 A 、 B 、 C 依次全部加鎖後才可以進行更新操作,那麼執行緒二的加鎖順序也必須是 A 、 B 、 C ,否則可能出現死鎖。
  • 【強制】並行修改同一記錄時,避免更新丟失,需要加鎖。要麼在應用層加鎖,要麼在快取加鎖,要麼在資料庫層使用樂觀鎖,使用 version 作為更新依據。說明:如果每次存取衝突概率小於 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數不得小於 3 次。
  • 【強制】多執行緒並行處理定時任務時, Timer 執行多個 TimeTask 時,只要其中之一沒有捕獲丟擲的異常,其它任務便會自動終止執行,使用 ScheduledExecutorService 則沒有這個問題。
  • 【推薦】使用 CountDownLatch 進行非同步轉同步操作,每個執行緒退出前必須呼叫 countDown方法,執行緒執行程式碼注意 catch 異常,確保 countDown 方法被執行到,避免主執行緒無法執行至 await 方法,直到超時才返回結果。
  • 【推薦】避免 Random 範例被多執行緒使用,雖然共用該範例是執行緒安全的,但會因競爭同一seed 導致的效能下降。Random 範例包括 java . util . Random 的範例或者 Math . random() 的方式。
  • 【推薦】在並行場景下,通過雙重檢查鎖 (double - checked locking) 實現延遲初始化的優化問題隱患 ( 可參考 The " Double - Checked Locking is Broken " Declaration) ,推薦解決方案中較為簡單一種 ( 適用於 JDK 5 及以上版本 ) ,將目標屬性宣告為 volatile 型 。
  • 【參考】 volatile 解決多執行緒記憶體不可見問題。對於一寫多讀,是可以解決變數同步問題,但是如果多寫,同樣無法解決執行緒安全問題。如果是 count ++操作,使用如下類實現:AtomicInteger count = new AtomicInteger(); count . addAndGet( 1 ); 如果是 JDK 8,推薦使用 LongAdder 物件,比 AtomicLong 效能更好 ( 減少樂觀鎖的重試次數 ) 。
  • 【參考】 HashMap 在容量不夠進行 resize 時由於高並行可能出現死鏈,導致 CPU 飆升,在開發過程中可以使用其它資料結構或加鎖來規避此風險。
  • 【參考】 ThreadLocal 無法解決共用物件的更新問題, ThreadLocal 物件建議使用 static修飾。這個變數是針對一個執行緒內所有操作共用的,所以設定為靜態變數,所有此類範例共用此靜態變數 ,也就是說在類第一次被使用時裝載,只分配一塊儲存空間,所有此類的物件 ( 只要是這個執行緒內定義的 ) 都可以操控這個變數。

控制語句

  • 【強制】在一個 switch 塊內,每個 case 要麼通過 break / return 等來終止,要麼註釋說明程式將繼續執行到哪一個 case 為止 ; 在一個 switch 塊內,都必須包含一個 default 語句並且放在最後,即使它什麼程式碼也沒有。
  • 【強制】在 if / else / for / while / do 語句中必須使用大括號。即使只有一行程式碼,避免採用單行的編碼方式: if (condition) statements;
  • 【推薦】表達異常的分支時,少用 if-else 方式 
  • 【強制】如果非得使用 if()...else if()...else... 方式表達邏輯,避免後續程式碼維護困難,請勿超過 3 層。超過 3 層的 if-else 的邏輯判斷程式碼可以使用衛語句、策略模式、狀態模式等來實現
  • 【推薦】除常用方法(如 getXxx/isXxx)等外,不要在條件判斷中執行其它複雜的語句,將複雜邏輯判斷的結果賦值給一個有意義的布林變數名,以提高可讀性。說明:很多 if 語句內的邏輯相當複雜,閱讀者需要分析條件表示式的最終結果,才能明確什麼樣的條件執行什麼樣的語句
  • 【推薦】迴圈體中的語句要考量效能,以下操作儘量移至迴圈體外處理,如定義物件、變數、獲取資料庫連線,進行不必要的 try - catch 操作 ( 這個 try - catch 是否可以移至迴圈體外)
  • 【推薦】介面入參保護,這種場景常見的是用於做批次操作的介面
  • 【參考】下列情形,需要進行引數校驗:
    • 呼叫頻次低的方法。
    • 執行時間開銷很大的方法。此情形中,引數校驗時間幾乎可以忽略不計,但如果因為引數錯誤導致中間執行回退,或者錯誤,那得不償失。
    • 需要極高穩定性和可用性的方法。
    • 對外提供的開放介面,不管是 RPC / API / HTTP 介面。
    • 敏感許可權入口。
  • 【參考】下列情形,不需要進行引數校驗:
    • 極有可能被迴圈呼叫的方法。但在方法說明裡必須註明外部引數檢查要求。
    • 底層呼叫頻度比較高的方法。畢竟是像純淨水過濾的最後一道,引數錯誤不太可能到底層才會暴露問題。一般 DAO 層與 Service 層都在同一個應用中,部署在同一臺伺服器中,所以 DAO 的引數校驗,可以省略。
    • 被宣告成 private 只會被自己程式碼所呼叫的方法,如果能夠確定呼叫方法的程式碼傳入引數已經做過檢查或者肯定不會有問題,此時可以不校驗引數。

註釋規約

  • 【強制】類、類屬性、類方法的註釋必須使用 Javadoc 規範,使用/**內容*/格式,不得使用// xxx 方式。說明:在 IDE 編輯視窗中, Javadoc 方式會提示相關注釋,生成 Javadoc 可以正確輸出相應註釋 ; 在 IDE 中,工程呼叫方法時,不進入方法即可懸浮提示方法、引數、返回值的意義,提高閱讀效率。
  • 【強制】所有的抽象方法 ( 包括介面中的方法 ) 必須要用 Javadoc 註釋、除了返回值、引數、異常說明外,還必須指出該方法做什麼事情,實現什麼功能。說明:對子類的實現要求,或者呼叫注意事項,請一併說明。
  • 【強制】所有的類都必須新增建立者和建立日期。
  • 【強制】方法內部單行註釋,在被註釋語句上方另起一行,使用//註釋。方法內部多行註釋使用/* */註釋,注意與程式碼對齊。
  • 【強制】所有的列舉型別欄位必須要有註釋,說明每個資料項的用途。
  • 【推薦】與其「半吊子」英文來註釋,不如用中文註釋把問題說清楚。專有名詞與關鍵字保持英文原文即可。
  • 【推薦】程式碼修改的同時,註釋也要進行相應的修改,尤其是引數、返回值、異常、核心邏輯等的修改。說明:程式碼與註釋更新不同步,就像路網與導航軟體更新不同步一樣,如果導航軟體嚴重滯後,就失去了導航的意義。
  • 【參考】謹慎註釋掉程式碼。在上方詳細說明,而不是簡單的註釋掉。如果無用,則刪除。說明:程式碼被註釋掉有兩種可能性:
    • 後續會恢復此段程式碼邏輯。
    • 永久不用。前者如果沒有備註資訊,難以知曉註釋動機。後者建議直接刪掉 ( 程式碼倉庫儲存了歷史程式碼 ) 。
    • 【參考】對於註釋的要求:第一、能夠準確反應設計思想和程式碼邏輯 ; 第二、能夠描述業務含義,使別的程式設計師能夠迅速瞭解到程式碼背後的資訊。完全沒有註釋的大段程式碼對於閱讀者形同天書,註釋是給自己看的,即使隔很長時間,也能清晰理解當時的思路 ; 註釋也是給繼任者看的,使其能夠快速接替自己的工作。
  • 【參考】好的命名、程式碼結構是自解釋的,註釋力求精簡準確、表達到位。避免出現註釋的一個極端:過多過濫的註釋,程式碼的邏輯一旦修改,修改註釋是相當大的負擔。
    • 【參考】特殊註釋標記,請註明標記人與標記時間。注意及時處理這些標記,通過標記掃描,經常清理此類標記。線上故障有時候就是來源於這些標記處的程式碼。
    • 待辦事宜 (TODO) : ( 標記人,標記時間, [ 預計處理時間 ])表示需要實現,但目前還未實現的功能。這實際上是一個 Javadoc 的標籤,目前的 Javadoc還沒有實現,但已經被廣泛使用。只能應用於類,介面和方法 ( 因為它是一個 Javadoc 標籤 ) 。
    • 錯誤,不能工作 (FIXME) : ( 標記人,標記時間, [ 預計處理時間 ])在註釋中用 FIXME 標記某程式碼是錯誤的,而且不能工作,需要及時糾正的情況。

其他

  • 【強制】在使用正規表示式時,利用好其預編譯功能,可以有效加快正則匹配速度。說明:不要在方法體內定義: Pattern pattern = Pattern . compile( 規則 );
  • 【強制】 velocity 呼叫 POJO 類的屬性時,建議直接使用屬性名取值即可,模板引擎會自動按規範呼叫 POJO 的 getXxx() ,如果是 boolean 基本資料型別變數 (boolean 命名不需要加 is字首 ) ,會自動呼叫 isXxx() 方法。說明:注意如果是 Boolean 包裝類物件,優先呼叫 getXxx() 的方法。
  • 【強制】後臺輸送給頁面的變數必須加 $!{var} ——中間的感嘆號。說明:如果 var = null 或者不存在,那麼 ${var} 會直接顯示在頁面上。
  • 【強制】注意 Math . random() 這個方法返回是 double 型別,注意取值的範圍 0≤ x <1 ( 能夠取到零值,注意除零異常 ) ,如果想獲取整數型別的亂數,不要將 x 放大 10 的若干倍然後取整,直接使用 Random 物件的 nextInt 或者 nextLong 方法。
  • 【強制】獲取當前毫秒數 System . currentTimeMillis(); 而不是 new Date() . getTime();說明:如果想獲取更加精確的納秒級時間值,使用 System . nanoTime()的方式 。在 JDK 8 中,針對統計時間等場景,推薦使用 Instant 類。
  • 【推薦】不要在檢視模板中加入任何複雜的邏輯。說明:根據 MVC 理論,檢視的職責是展示,不要搶模型和控制器的活。
  • 【推薦】任何資料結構的構造或初始化,都應指定大小,避免資料結構無限增長吃光記憶體。
  • 【推薦】及時清理不再使用的程式碼段或設定資訊。說明:對於垃圾程式碼或過時設定,堅決清理乾淨,避免程式過度臃腫,程式碼冗餘。

異常紀錄檔

例外處理

  • 【強制】 Java 類庫中定義的一類 RuntimeException 可以通過預先檢查進行規避,而不應該通過 catch 來處理,比如: IndexOutOfBoundsException , NullPointerException 等等。說明:無法通過預檢查的異常除外,如在解析一個外部傳來的字串形式數位時,通過 catch NumberFormatException 來實現。
  • 【強制】異常不要用來做流程控制,條件控制,因為異常的處理效率比條件分支低。
  • 【強制】對大段程式碼進行 try - catch ,這是不負責任的表現。 catch 時請分清穩定程式碼和非穩定程式碼,穩定程式碼指的是無論如何不會出錯的程式碼。對於非穩定程式碼的 catch 儘可能進行區分異常型別,再做對應的例外處理。
  • 【強制】捕獲異常是為了處理它,不要捕獲了卻什麼都不處理而拋棄之,如果不想處理它,請將該異常拋給它的呼叫者。最外層的業務使用者,必須處理異常,將其轉化為使用者可以理解的內容。
  • 【強制】有 try 塊放到了事務程式碼中, catch 異常後,如果需要回滾事務,一定要注意手動回滾事務。
  • 【強制】 finally 塊必須對資源物件、流物件進行關閉,有異常也要做 try - catch 。說明:如果 JDK 7 及以上,可以使用 try - with - resources 方式。
  • 【強制】不能在 finally 塊中使用 return , finally 塊中的 return 返回後方法結束執行,不會再執行 try 塊中的 return 語句。
  • 【強制】捕獲異常與拋異常,必須是完全匹配,或者捕獲異常是拋異常的父類別。說明:如果預期對方拋的是繡球,實際接到的是鉛球,就會產生意外情況。
  • 【推薦】方法的返回值可以為 null ,不強制返回空集合,或者空物件等,必須新增註釋充分說明什麼情況下會返回 null 值。呼叫方需要進行 null 判斷防止 NPE 問題。說明:本手冊明確防止 NPE 是呼叫者的責任。即使被呼叫方法返回空集合或者空物件,對呼叫者來說,也並非高枕無憂,必須考慮到遠端呼叫失敗、序列化失敗、執行時異常等場景返回null 的情況。
  • 【推薦】防止 NPE ,是程式設計師的基本修養,注意 NPE 產生的場景:
    • 返回型別為基本資料型別,return 包裝資料型別的物件時,自動拆箱有可能產生 NPE。
    • 資料庫的查詢結果可能為 null 。
    • 集合裡的元素即使 isNotEmpty ,取出的資料元素也可能為 null 。
    • 遠端呼叫返回物件時,一律要求進行空指標判斷,防止 NPE 。
    • 對於 Session 中獲取的資料,建議 NPE 檢查,避免空指標。
    • 級聯呼叫 obj . getA() . getB() . getC(); 一連串呼叫,易產生 NPE 。
  • 【推薦】定義時區分 unchecked / checked 異常,避免直接丟擲 new RuntimeException() ,更不允許丟擲 Exception 或者 Throwable ,應使用有業務含義的自定義異常。推薦業界已定義過的自定義異常,如: DAOException / ServiceException 等。
  • 【參考】在程式碼中使用「拋異常」還是「返回錯誤碼」,對於公司外的 http / api 開放介面必須使用「錯誤碼」 ; 而應用內部推薦異常丟擲 ; 跨應用間 RPC 呼叫優先考慮使用 Result 方式,封裝 isSuccess()方法 、「錯誤碼」、「錯誤簡簡訊息」。說明:關於 RPC 方法返回方式使用 Result 方式的理由:
    • 使用拋異常返回方式,呼叫方如果沒有捕獲到就會產生執行時錯誤。
    • 如果不加棧資訊,只是 new 自定義異常,加入自己的理解的 error message ,對於呼叫端解決問題的幫助不會太多。如果加了棧資訊,在頻繁呼叫出錯的情況下,資料序列化和傳輸的效能損耗也是問題。
  • 【參考】避免出現重複的程式碼 (Don ’ t Repeat Yourself) ,即 DRY 原則。

紀錄檔規約

  • 【強制】應用中不可直接使用紀錄檔系統 (Log 4j 、 Logback) 中的 API ,而應依賴使用紀錄檔框架SLF 4 J 中的 API ,使用門面模式的紀錄檔框架,有利於維護和各個類的紀錄檔處理方式統一。
  • 【強制】紀錄檔檔案推薦至少儲存 15 天,因為有些異常具備以「周」為頻次發生的特點。
  • 【強制】應用中的擴充套件紀錄檔 ( 如打點、臨時監控、存取紀錄檔等 ) 命名方式:appName _ logType _ logName . log 。 logType :紀錄檔型別,推薦分類有stats / desc / monitor / visit 等 ;logName :紀錄檔描述。這種命名的好處:通過檔名就可知道紀錄檔檔案屬於什麼應用,什麼型別,什麼目的,也有利於歸類查詢。
  • 【強制】對 trace / debug / info 級別的紀錄檔輸出,必須使用條件輸出形式或者使用預留位置的方式。說明: logger . debug( " Processing trade with id : " + id + " and symbol : " + symbol);
  • 【強制】避免重複列印紀錄檔,浪費磁碟空間,務必在 log4j.xml 中設定 additivity = false
  • 【強制】異常資訊應該包括兩類資訊:案發現場資訊和異常堆疊資訊。如果不處理,那麼通過關鍵字 throws 往上丟擲。
  • 【推薦】謹慎地記錄紀錄檔。生產環境禁止輸出 debug 紀錄檔 ; 有選擇地輸出 info 紀錄檔 ; 如果使用 warn 來記錄剛上線時的業務行為資訊,一定要注意紀錄檔輸出量的問題,避免把伺服器磁碟撐爆,並記得及時刪除這些觀察紀錄檔。說明:大量地輸出無效紀錄檔,不利於系統效能提升,也不利於快速定位錯誤點。記錄紀錄檔時請
  • 【參考】可以使用 warn 紀錄檔級別來記錄使用者輸入引數錯誤的情況,避免使用者投訴時,無所適從。注意紀錄檔輸出的級別, error 級別只記錄系統邏輯出錯、異常等重要的錯誤資訊。如非必要,請不要在此場景打出 error 級別。

單元測試

  • 【強制】好的單元測試必須遵守 AIR 原則。說明:單元測試線上上執行時,感覺像空氣 (AIR) 一樣並不存在,但在測試質量的保障上,卻是非常關鍵的。好的單元測試宏觀上來說,具有自動化、獨立性、可重複執行的特點。
    • A: Automatic (自動化)
    • I: Independent (獨立性)
    • R: Repeatable (可重複)
  • 【強制】單元測試應該是全自動執行的,並且非互動式的。測試框架通常是定期執行的,執行過程必須完全自動化才有意義。輸出結果需要人工檢查的測試不是一個好的單元測試。單元測試中不準使用 System.out 來進行人肉驗證,必須使用 assert 來驗證。
  • 【強制】保持單元測試的獨立性。為了保證單元測試穩定可靠且便於維護,單元測試用例之間決不能互相呼叫,也不能依賴執行的先後次序。
  • 【強制】單元測試是可以重複執行的,不能受到外界環境的影響。說明:單元測試通常會被放到持續整合中,每次有程式碼 check in 時單元測試都會被執行。如果單測對外部環境(網路、服務、中介軟體等)有依賴,容易導致持續整合機制的不可用。
  • 【強制】對於單元測試,要保證測試粒度足夠小,有助於精確定位問題。單測粒度至多是類級別,一般是方法級別。說明:只有測試粒度小才能在出錯時儘快定位到出錯位置。單測不負責檢查跨類或者跨系統的互動邏輯,那是整合測試的領域。
  • 【強制】核心業務、核心應用、核心模組的增量程式碼確保單元測試通過。說明:新增程式碼及時補充單元測試,如果新增程式碼影響了原有單元測試,請及時修正。
  • 【強制】單元測試程式碼必須寫在如下工程目錄: src/test/java ,不允許寫在業務程式碼目錄下。說明:原始碼構建時會跳過此目錄,而單元測試框架預設是掃描此目錄。
  • 【推薦】單元測試的基本目標:語句覆蓋率達到 70% ;核心模組的語句覆蓋率和分支覆蓋率都要達到 100% 說明:在工程規約的應用分層中提到的 DAO 層, Manager 層,可重用度高的 Service ,都應該進行單元測試。
  • 【推薦】編寫單元測試程式碼遵守 BCDE 原則,以保證被測試模組的交付質量。
    • B: Border ,邊界值測試,包括迴圈邊界、特殊取值、特殊時間點、資料順序等。
    • C: Correct ,正確的輸入,並得到預期的結果。
    • D: Design ,與設計檔案相結合,來編寫單元測試。
    • E: Error ,強制錯誤資訊輸入(如:非法資料、異常流程、非業務允許輸入等),並得到預期的結果。
  • 【推薦】對於資料庫相關的查詢,更新,刪除等操作,不能假設資料庫裡的資料是存在的,或者直接運算元據庫把資料插入進去,請使用程式插入或者匯入資料的方式來準備資料。
  • 【推薦】和資料庫相關的單元測試,可以設定自動回滾機制,不給資料庫造成髒資料。或者對單元測試產生的資料有明確的前字尾標識。
  • 【推薦】對於不可測的程式碼建議做必要的重構,使程式碼變得可測,避免為了達到測試要求而書寫不規範測試程式碼。
  • 【推薦】在設計評審階段,開發人員需要和測試人員一起確定單元測試範圍,單元測試最好覆蓋所有測試用例( UC )。
  • 【推薦】單元測試作為一種質量保障手段,不建議專案發布後補充單元測試用例,建議在專案提測前完成單元測試。
  • 【參考】為了更方便地進行單元測試,業務程式碼應避免以下情況:
    • 構造方法中做的事情過多。
    • 存在過多的全域性變數和靜態方法。
    • 存在過多的外部依賴。
    • 存在過多的條件語句。
    • 說明:多層條件語句建議使用衛語句、策略模式、狀態模式等方式重構。
  • 【參考】不要對單元測試存在如下誤解:
    • 那是測試同學乾的事情。本文是開發手冊,凡是本文內容都是與開發同學強相關的。
    • 單元測試程式碼是多餘的。汽車的整體功能與各單元部件的測試正常與否是強相關的。
    • 單元測試程式碼不需要維護。一年半載後,那麼單元測試幾乎處於廢棄狀態。
    • 單元測試與線上故障沒有辯證關係。好的單元測試能夠最大限度地規避線上故障。

安全規約

  • 【強制】隸屬於使用者個人的頁面或者功能必須進行許可權控制校驗。說明:防止沒有做水平許可權校驗就可隨意存取、修改、刪除別人的資料,比如檢視他人的私信內容、修改他人的訂單。
  • 【強制】使用者敏感資料禁止直接展示,必須對展示資料進行脫敏。說明:檢視個人手機號碼會顯示成:158****9119,隱藏中間 4 位,防止隱私洩露。
  • 【強制】使用者輸入的 SQL 引數嚴格使用引數繫結或者 METADATA 欄位值限定,防止 SQL 注入,禁止字串拼接 SQL 存取資料庫。
  • 【強制】使用者請求傳入的任何引數必須做有效性驗證。說明:忽略引數校驗可能導致:
    • page size 過大導致記憶體溢位
    • 惡意 order by 導致資料庫慢查詢
    • 任意重定向
    • SQL 注入
    • 反序列化注入
    • 正則輸入源串拒絕服務 ReDoS
    • 說明:Java 程式碼用正則來驗證使用者端的輸入,有些正則寫法驗證普通使用者輸入沒有問題,但是如果攻擊人員使用的是特殊構造的字串來驗證,有可能導致死迴圈的結果。
  • 【強制】禁止向 HTML 頁面輸出未經安全過濾或未正確跳脫的使用者資料。
  • 【強制】表單、 AJAX 提交必須執行 CSRF 安全過濾。說明: CSRF(Cross - site request forgery) 跨站請求偽造是一類常見程式設計漏洞。對於存在CSRF 漏洞的應用/網站,攻擊者可以事先構造好 URL ,只要受害者使用者一存取,後臺便在使用者不知情情況下對資料庫中使用者引數進行相應修改。
  • 【強制】在使用平臺資源,譬如簡訊、郵件、電話、下單、支付,必須實現正確的防重放限制,如數量限制、疲勞度控制、驗證碼校驗,避免被濫刷、資損。說明:如註冊時傳送驗證碼到手機,如果沒有限制次數和頻率,那麼可以利用此功能騷擾到其它使用者,並造成簡訊平臺資源浪費。
  • 【推薦】發貼、評論、傳送即時訊息等使用者生成內容的場景必須實現防刷、文字內容違禁詞過濾等風控策略。

資料庫

建表規約

  • 【強制】表達是與否概念的欄位,必須使用 is _ xxx 的方式命名,資料型別是 unsigned tinyint( 1 表示是,0 表示否 ) 。說明:任何欄位如果為非負數,必須是 unsigned 。
  • 【強制】表名、欄位名必須使用小寫字母或數位 , 禁止出現數位開頭,禁止兩個下劃線中間只出現數位。資料庫欄位名的修改代價很大,因為無法進行預釋出,所以欄位名稱需要慎重考慮。說明:MySQL 在 Windows 下不區分大小寫,但在 Linux 下預設是區分大小寫。因此,資料庫名、表名、欄位名,都不允許出現任何大寫字母,避免節外生枝。
  • 【強制】表名不使用複數名詞。說明:表名應該僅僅表示表裡面的實體內容,不應該表示實體數量,對應於 DO 類名也是單數形式,符合表達習慣。
  • 【強制】禁用保留字,如 desc 、 range 、 match 、 delayed 等,請參考 MySQL 官方保留字。
  • 【強制】主鍵索引名為 pk_ 欄位名;唯一索引名為 uk _欄位名 ; 普通索引名則為 idx _欄位名。說明: pk_ 即 primary key;uk _ 即 unique key;idx _ 即 index 的簡稱。
  • 【強制】小數型別為 decimal ,禁止使用 float 和 double 。說明: float 和 double 在儲存的時候,存在精度損失的問題,很可能在值的比較時,得到不正確的結果。如果儲存的資料範圍超過 decimal 的範圍,建議將資料拆成整數和小數分開儲存。
  • 【強制】如果儲存的字串長度幾乎相等,使用 char 定長字串型別。
  • 【強制】 varchar 是可變長字串,不預先分配儲存空間,長度不要超過 5000,如果儲存長度大於此值,定義欄位型別為 text ,獨立出來一張表,用主鍵來對應,避免影響其它欄位索引效率。
  • 【強制】表必備三欄位: id , gmt _ create , gmt _ modified 。說明:其中 id 必為主鍵,型別為 unsigned bigint 、單表時自增、步長為 1。 gmt_create,gmt_modified 的型別均為 date_time 型別,前者現在時表示主動建立,後者過去分詞表示被動更新。
  • 【推薦】表的命名最好是加上「業務名稱_表的作用」。正例: alipay _ task / force _ project / trade _ config
  • 【推薦】庫名與應用名稱儘量一致。
  • 【推薦】如果修改欄位含義或對欄位表示的狀態追加時,需要及時更新欄位註釋。
  • 【推薦】欄位允許適當冗餘,以提高查詢效能,但必須考慮資料一致。冗餘欄位應遵循:
    • 不是頻繁修改的欄位。
    • 不是 varchar 超長欄位,更不能是 text 欄位。
  • 【推薦】單錶行數超過 500 萬行或者單表容量超過 2 GB ,才推薦進行分庫分表。說明:如果預計三年後的資料量根本達不到這個級別,請不要在建立表時就分庫分表。
  • 【參考】合適的字元儲存長度,不但節約資料庫表空間、節約索引儲存,更重要的是提升檢索速度。

索引規約

  • 【強制】業務上具有唯一特性的欄位,即使是多個欄位的組合,也必須建成唯一索引。說明:不要以為唯一索引影響了 insert 速度,這個速度損耗可以忽略,但提高查詢速度是明顯的 ; 另外,即使在應用層做了非常完善的校驗控制,只要沒有唯一索引,根據墨菲定律,必然有髒資料產生。
  • 【強制】超過三個表禁止 join 。需要 join 的欄位,資料型別必須絕對一致 ; 多表關聯查詢時,保證被關聯的欄位需要有索引。說明:即使雙表 join 也要注意表索引、 SQL 效能。
  • 【強制】在 varchar 欄位上建立索引時,必須指定索引長度,沒必要對全欄位建立索引,根據實際文字區分度決定索引長度即可。說明:索引的長度與區分度是一對矛盾體,一般對字串型別資料,長度為 20 的索引,區分度會高達 90%以上,可以使用 count(distinct left( 列名, 索引長度 )) / count( * ) 的區分度來確定。
  • 【強制】頁面搜尋嚴禁左模糊或者全模糊,如果需要請走搜尋引擎來解決。說明:索引檔案具有 B - Tree 的最左字首匹配特性,如果左邊的值未確定,那麼無法使用此索引。
  • 【推薦】如果有 order by 的場景,請注意利用索引的有序性。 order by 最後的欄位是組合索引的一部分,並且放在索引組合順序的最後,避免出現 file _ sort 的情況,影響查詢效能。
  • 【推薦】利用覆蓋索引來進行查詢操作,避免回表。說明:如果一本書需要知道第 11 章是什麼標題,會翻開第 11 章對應的那一頁嗎?目錄瀏覽一下就好,這個目錄就是起到覆蓋索引的作用。
  • 【推薦】利用延遲關聯或者子查詢優化超多分頁場景。說明: MySQL 並不是跳過 offset 行,而是取 offset + N 行,然後返回放棄前 offset 行,返回N 行,那當 offset 特別大的時候,效率就非常的低下,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行 SQL 改寫。
  • 【推薦】 SQL 效能優化的目標:至少要達到 range 級別,要求是 ref 級別,如果可以是 consts最好。
    • consts 單表中最多隻有一個匹配行 ( 主鍵或者唯一索引 ) ,在優化階段即可讀取到資料。
    • ref 指的是使用普通的索引 (normal index)
    • range 對索引進行範圍檢索。
  • 【推薦】建組合索引的時候,區分度最高的在最左邊。
  • 【推薦】防止因欄位型別不同造成的隱式轉換,導致索引失效。
  • 【參考】建立索引時避免有如下極端誤解:
    • 寧濫勿缺。認為一個查詢就需要建一個索引。
    • 寧缺勿濫。認為索引會消耗空間、嚴重拖慢更新和新增速度。
    • 抵制惟一索引。認為業務的惟一性一律需要在應用層通過「先查後插」方式解決。

SQL語句

  • 【強制】不要使用 count( 列名 ) 或 count( 常數 ) 來替代 count( * ) , count( * ) 是 SQL 92 定義的標準統計行數的語法,跟資料庫無關,跟 NULL 和非 NULL 無關。說明: count( * ) 會統計值為 NULL 的行,而 count( 列名 ) 不會統計此列為 NULL 值的行。
  • 【強制】 count(distinct col) 計算該列除 NULL 之外的不重複行數,注意 count(distinct col 1, col 2 ) 如果其中一列全為 NULL ,那麼即使另一列有不同的值,也返回為 0。
  • 【強制】當某一列的值全是 NULL 時, count(col) 的返回結果為 0,但 sum(col) 的返回結果為NULL ,因此使用 sum() 時需注意 NPE 問題。
  • 【強制】使用 ISNULL() 來判斷是否為 NULL 值。說明: NULL 與任何值的直接比較都為 NULL。
    • NULL<>NULL 的返回結果是 NULL ,而不是 false 。
    • NULL=NULL 的返回結果是 NULL ,而不是 true 。
    • NULL<>1 的返回結果是 NULL ,而不是 true 。
  • 【強制】 在程式碼中寫分頁查詢邏輯時,若 count 為 0 應直接返回,避免執行後面的分頁語句。
  • 【強制】不得使用外來鍵與級聯,一切外來鍵概念必須在應用層解決。
  • 【強制】禁止使用儲存過程,儲存過程難以偵錯和擴充套件,更沒有移植性。
  • 【強制】資料訂正時,刪除和修改記錄時,要先 select ,避免出現誤刪除,確認無誤才能執行更新語句。
  • 【推薦】 in 操作能避免則避免,若實在避免不了,需要仔細評估 in 後邊的集合元素數量,控制在 1000 個之內。
  • 【參考】如果有全球化需要,所有的字元儲存與表示,均以 utf -8 編碼,注意字元統計函數的區別。
  • 【參考】 TRUNCATE TABLE 比 DELETE 速度快,且使用的系統和事務紀錄檔資源少,但 TRUNCATE 無事務且不觸發 trigger ,有可能造成事故,故不建議在開發程式碼中使用此語句。

ORM對映

  • 【強制】在表查詢中,一律不要使用 * 作為查詢的欄位列表,需要哪些欄位必須明確寫明。說明:
    • 增加查詢分析器解析成本。
    • 增減欄位容易與 resultMap 設定不一致。
  • 【強制】 POJO 類的 布林 屬性不能加 is ,而資料庫欄位必須加 is _,要求在 resultMap 中進行欄位與屬性之間的對映。
  • 【強制】不要用 resultClass 當返回引數,即使所有類屬性名與資料庫欄位一一對應,也需要定義 ; 反過來,每一個表也必然有一個與之對應。
  • 【強制】sql. xml 設定引數使用:#{},# param # 不要使用${} 此種方式容易出現 SQL 注入。
  • 【強制】 iBATIS 自帶的 queryForList(String statementName , int start , int size) 不推薦使用。說明:其實現方式是在資料庫取到 statementName 對應的 SQL 語句的所有記錄,再通過 subList取 start , size 的子集合。
  • 【強制】不允許直接拿 HashMap 與 Hashtable 作為查詢結果集的輸出。
  • 【強制】更新資料表記錄時,必須同時更新記錄對應的 gmt _ modified 欄位值為當前時間。
  • 【推薦】不要寫一個大而全的資料更新介面。傳入為 POJO 類,不管是不是自己的目標更新欄位,都進行 update table set c1=value1,c2=value2,c3=value3; 這是不對的。執行 SQL時,不要更新無改動的欄位,一是易出錯 ; 二是效率低 ; 三是增加 binlog 儲存。
  • 【參考】@ Transactional 事務不要濫用。事務會影響資料庫的 QPS ,另外使用事務的地方需要考慮各方面的回滾方案,包括快取回滾、搜尋引擎回滾、訊息補償、統計修正等。
  • 【參考】< isEqual >中的 compareValue 是與屬性值對比的常數,一般是數位,表示相等時帶上此條件 ; < isNotEmpty >表示不為空且不為 null 時執行 ; < isNotNull >表示不為 null 值時執行。

工程結構

應用分層

  • 【推薦】圖中預設上層依賴於下層,箭頭關係表示可直接依賴,如:開放介面層可以依賴於Web 層,也可以直接依賴於 Service 層,依此類推:
    • 開放介面層:可直接封裝 Service 方法暴露成 RPC 介面 ; 通過 Web 封裝成 http 介面 ;進行閘道器安全控制、流量控制等。
    • 終端顯示層:各個端的模板渲染並執行顯示的層。當前主要是 velocity 渲染, JS 渲染,JSP 渲染,行動端展示等。
    • Web 層:主要是對存取控制進行轉發,各類基本引數校驗,或者不復用的業務簡單處理等。
    • Service 層:相對具體的業務邏輯服務層。
    • Manager 層 :通用業務處理層,它有如下特徵:
      • 對第三方平臺封裝的層,預處理返回結果及轉化異常資訊 ;
      • 對 Service 層通用能力的下沉,如快取方案、中介軟體通用處理 ;
      • 與 DAO 層互動,對多個 DAO 的組合複用。
    • DAO 層:資料存取層,與底層 MySQL 、 Oracle 、 Hbase 等 進行資料互動。
    • 外部介面或第三方平臺:包括其它部門 RPC 開放介面,基礎平臺,其它公司的 HTTP 介面。
  • 【參考】 ( 分層例外處理規約 ) 在 DAO 層,產生的異常型別有很多,無法用細粒度的異常進行 catch ,使用 catch(Exception e) 方式,並 throw new DAOException(e) ,不需要列印紀錄檔,因為紀錄檔在 Manager / Service 層一定需要捕獲並打到紀錄檔檔案中去,如果同臺伺服器再打紀錄檔,浪費效能和儲存。在 Service 層出現異常時,必須記錄出錯紀錄檔到磁碟,儘可能帶上引數資訊,相當於保護案發現場。如果 Manager 層與 Service 同機部署,紀錄檔方式與 DAO層處理一致,如果是單獨部署,則採用與 Service 一致的處理方式。 Web 層絕不應該繼續往上拋異常,因為已經處於頂層,如果意識到這個異常將導致頁面無法正常渲染,那麼就應該直接跳轉到友好錯誤頁面,加上使用者容易理解的錯誤提示資訊。開放介面層要將例外處理成錯誤碼和錯誤資訊方式返回。
  • 【參考】分層領域模型規約:
    • 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 類來傳輸。

二方庫依賴

  • 【強制】定義 GAV 遵從以下規則:
    • GroupID 格式: com .{公司/ BU }.業務線. [ 子業務線 ] ,最多 4 級。說明:{公司/ BU } 例如: alibaba / taobao / tmall / aliexpress 等 BU 一級 ; 子業務線可選。正例: com . taobao . jstorm 或 com.alibaba.dubbo.register
    • ArtifactID 格式:產品線名-模組名。語意不重複不遺漏,先到中央倉庫去查證一下。正例: dubbo - client / fastjson - api / jstorm - tool
    • Version :詳細規定參考下方。
  • 【強制】二方庫版本號命名方式:主版本號.次版本號.修訂號
    • 主版本號:產品方向改變,或者大規模 API 不相容,或者架構不相容升級。
    • 次版本號:保持相對相容性,增加主要功能特性,影響範圍極小的 API 不相容修改。
    • 修訂號:保持完全相容性,修復 BUG 、新增次要功能特性等。
    • 注意起始版本號必須為: 1.0.0 ,而不是 0.0.1 正式釋出的類庫必須先去中央倉庫進行查證,使版本號有延續性,正式版本號不允許覆蓋升級。如當前版本:1.3.3,那麼下一個合理的版本號:1.3.4 或 1.4.0 或 2.0.0
  • 【強制】線上應用不要依賴 SNAPSHOT 版本 ( 安全包除外 )。不依賴 SNAPSHOT 版本是保證應用釋出的冪等性。另外,也可以加快編譯時的打包構建。
  • 【強制】二方庫的新增或升級,保持除功能點之外的其它 jar 包仲裁結果不變。如果有改變,必須明確評估和驗證,建議進行 dependency : resolve 前後資訊比對,如果仲裁結果完全不一致,那麼通過 dependency : tree 命令,找出差異點,進行< excludes >排除 jar 包。
  • 【強制】二方庫裡可以定義列舉型別,引數可以使用列舉型別,但是介面返回值不允許使用列舉型別或者包含列舉型別的 POJO 物件。
  • 【強制】依賴於一個二方庫群時,必須定義一個統一的版本變數,避免版本號不一致。說明:依賴 springframework - core ,- context ,- beans ,它們都是同一個版本,可以定義一個變數來儲存版本:${ spring . version },定義依賴的時候,參照該版本。
  • 【強制】禁止在子專案的 pom 依賴中出現相同的 GroupId ,相同的 ArtifactId ,但是不同的Version 。說明:在本地偵錯時會使用各子專案指定的版本號,但是合併成一個 war ,只能有一個版本號出現在最後的 lib 目錄中。可能出現線下偵錯是正確的,釋出到線上卻出故障的問題。
  • 【推薦】所有 pom 檔案中的依賴宣告放在< dependencies >語句塊中,所有版本仲裁放在< dependencyManagement >語句塊中。說明:< dependencyManagement >裡只是宣告版本,並不實現引入,因此子專案需要顯式的宣告依賴, version 和 scope 都讀取自父 pom 。而< dependencies >所有宣告在主 pom 的< dependencies >裡的依賴都會自動引入,並預設被所有的子專案繼承。
  • 【推薦】二方庫不要有設定項,最低限度不要再增加設定項。
  • 【參考】為避免應用二方庫的依賴衝突問題,二方庫釋出者應當遵循以下原則:
    • 精簡可控原則。移除一切不必要的 API 和依賴,只包含 Service API 、必要的領域模型物件、 Utils 類、常數、列舉等。如果依賴其它二方庫,儘量是 provided 引入,讓二方庫使用者去依賴具體版本號 ; 無 log 具體實現,只依賴紀錄檔框架。
    • 穩定可追溯原則。每個版本的變化應該被記錄,二方庫由誰維護,原始碼在哪裡,都需要能方便查到。除非使用者主動升級版本,否則公共二方庫的行為不應該發生變化。

伺服器

  • 【推薦】高並行伺服器建議調小 TCP 協定的 time _ wait 超時時間。說明:作業系統預設 240 秒後,才會關閉處於 time _ wait 狀態的連線,在高並行存取下,伺服器端會因為處於 time _ wait 的連線數太多,可能無法建立新的連線,所以需要在伺服器上調小此等待值。
  • 【推薦】調大伺服器所支援的最大檔案控制程式碼數 (File Descriptor ,簡寫為 fd) 。說明:主流作業系統的設計是將 TCP / UDP 連線採用與檔案一樣的方式去管理,即一個連線對應於一個 fd 。主流的 linux 伺服器預設所支援最大 fd 數量為 1024,當並行連線數很大時很容易因為 fd 不足而出現「 open too many files 」錯誤,導致新的連線無法建立。 建議將 linux伺服器所支援的最大控制程式碼數調高數倍 ( 與伺服器的記憶體數量相關 ) 。
  • 【推薦】給 JVM 設定- XX :+ HeapDumpOnOutOfMemoryError 引數,讓 JVM 碰到 OOM 場景時輸出dump 資訊。說明: OOM 的發生是有概率的,甚至有規律地相隔數月才出現一例,出現時的現場資訊對查錯非常有價值。
  • 【推薦】線上上生產環境, JVM 的 Xms 和 Xmx 設定一樣大小的記憶體容量,避免在 GC 後調整堆大小帶來的壓力。
  • 【參考】伺服器內部重定向使用 forward; 外部重定向地址使用 URL 拼裝工具類來生成,否則會帶來 URL 維護不一致的問題和潛在的安全風險。