java开发规约
編程規約
命名風格
PO / UID 等。
正例:ForceCode / UserDO / HtmlDTO / XmlService / TcpUdpDeal / TaPromotion
反例:forcecode / UserDo / HTMLDto / XMLService / TCPUDPDeal / TAPromotion
命名以它要測試的類的名稱開始,以 Test 結尾。
3.【強制】包名統一使用小寫,點分隔符之間有且僅有一個自然語義的英語單詞。包名統一使用
單數形式,但是類名如果有復數含義,類名可以使用復數形式。
正例:應用工具類包名為 com.alibaba.ei.kunlun.aap.util、類名為 MessageUtils(此規則參考 spring 的
框架結構)
4.【強制】杜絕完全不規范的縮寫,避免望文不知義。
反例:AbstractClass“縮寫”成 AbsClass;condition“縮寫”成 condi;Function 縮寫”成 Fu,此類
隨意縮寫嚴重降低了代碼的可閱讀性。
5.【推薦】為了達到代碼自解釋的目標,任何自定義編程元素在命名時,使用盡量完整的單詞組
合來表達。
正例:對某個對象引用的 volatile 字段進行原子更新的類名為 AtomicReferenceFieldUpdater。
反例:常見的方法內變量為 int a;的定義方式。
6.【推薦】如果模塊、接口、類、方法使用了設計模式,在命名時需體現出具體模式。
說明:將設計模式體現在名字中,有利于閱讀者快速理解架構設計理念。
正例: public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;
7.【參考】枚舉類名帶上 Enum 后綴,枚舉成員名稱需要全大寫,單詞間用下劃線隔開。
說明:枚舉其實就是特殊的常量類,且構造方法被默認強制是私有。
正例:枚舉名字為 ProcessStatusEnum 的成員名稱:SUCCESS / UNKNOWN_REASON。
8.【參考】各層命名規約:
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。
常量定義
數字混淆,造成誤解。
說明:Long a = 2l; 寫的是數字的 21,還是 Long 型的 2?
3.【推薦】不要使用一個常量類維護所有常量,要按常量功能進行歸類,分開維護。
說明:大而全的常量類,雜亂無章,使用查找功能才能定位到修改的常量,不利于理解,也不利于維護。
正例:緩存相關常量放在類 CacheConsts 下;系統配置相關常量放在類 SystemConfigConsts 下。
4.【推薦】常量的復用層次有五層:跨應用共享常量、應用內共享常量、子工程內共享常量、包
內共享常量、類內共享常量。
1) 跨應用共享常量:放置在二方庫中,通常是 client.jar 中的 constant 目錄下。
2) 應用內共享常量:放置在一方庫中,通常是子模塊中的 constant 目錄下。
反例:易懂變量也要統一定義成應用內共享常量,兩位工程師在兩個類中分別定義了“YES”的變量:
類 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.【強制】注釋的雙斜線與注釋內容之間有且僅有一個空格。
正例:
// 這是示例注釋,請注意在雙斜線之后有一個空格
String commentString = new String();
2.【推薦】單個方法的總行數不超過 80 行。
說明:除注釋之外的方法簽名、左右大括號、方法內代碼、空行、回車及任何不可見字符的總行數不超過
80 行。
正例:代碼邏輯分清紅花和綠葉,個性和共性,綠葉邏輯單獨出來成為額外方法,使主干代碼更加清晰;共
性邏輯抽取成為共性方法,便于復用和維護。
3.【推薦】不同邏輯、不同語義、不同業務的代碼之間插入一個空行分隔開來以提升可讀性。
說明:任何情形,沒有必要插入多個空行進行隔開。
OOP規約(面對對象程序設計規約)
1.【強制】Object 的 equals 方法容易拋空指針異常,應使用常量或確定有值的對象來調用 equals。
正例:“test”.equals(object);
反例:object.equals(“test”);
說明:推薦使用 JDK7 引入的工具類 java.util.Objects#equals(Object a, Object b)
2.【強制】所有整型包裝類對象之間值的比較,全部使用 equals 方法比較。
說明:對于 Integer var = ? 在-128 至 127 之間的賦值,Integer 對象是在 IntegerCache.cache 產生,
會復用已有對象,這個區間內的 Integer 值可以直接使用== 進行判斷,但是這個區間之外的所有數據,都
會在堆上產生,并不會復用已有對象,這是一個大坑,推薦使用 equals 方法進行判斷。
3.【強制】浮點數之間的等值判斷,基本數據類型不能用==來比較,包裝數據類型不能用 equals
來判斷。
說明:浮點數采用“尾數+階碼”的編碼方式,類似于科學計數法的“有效數字+指數”的表示方式。二進
制無法精確表示大部分的十進制小數,具體原理參考《碼出高效》。
反例:
float a = 1.0F - 0.9F;
float b = 0.9F - 0.8F;
if (a == b) {
// 預期進入此代碼塊,執行其它業務邏輯
// 但事實上 a==b 的結果為 false
}
Float x = Float.valueOf(a);
Float y = Float.valueOf(b);
if (x.equals(y)) {
// 預期進入此代碼塊,執行其它業務邏輯
// 但事實上 equals 的結果為 false
}
正例:
(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?;
if (x.compareTo(y) == 0) {
System.out.println(“true”);
}
4.【強制】如上所示 BigDecimal 的等值比較應使用 compareTo()方法,而不是 equals()方法。
說明:equals()方法會比較值和精度(1.0 與 1.00 返回結果為 false),而 compareTo()則會忽略精度。
5.【強制】禁止使用構造方法 BigDecimal(double)的方式把 double 值轉化為 BigDecimal 對象。
說明:BigDecimal(double)存在精度損失風險,在精確計算或值比較的場景中可能會導致業務邏輯異常。
如:BigDecimal g = new BigDecimal(0.1F); 實際的存儲值為:0.10000000149
正例:優先推薦入參為 String 的構造方法,或使用 BigDecimal 的 valueOf 方法,此方法內部其實執行了
Double 的 toString,而 Double 的 toString 按 double 的實際能表達的精度對尾數進行了截斷。
BigDecimal recommend1 = new BigDecimal(“0.1”);
BigDecimal recommend2 = BigDecimal.valueOf(0.1);
6.關于基本數據類型與包裝數據類型的使用標準如下:
1) 【強制】所有的 POJO(DO、DTO、VO) 類屬性必須使用包裝數據類型。
2) 【強制】RPC 方法的返回值和參數必須使用包裝數據類型。
3) 【推薦】所有的局部變量使用基本數據類型。
說明:POJO 類屬性沒有初值是提醒使用者在需要使用時,必須自己顯式地進行賦值,任何 NPE 問題,或
者入庫檢查,都由使用者來保證。
正例:數據庫的查詢結果可能是 null,因為自動拆箱,用基本數據類型接收有 NPE 風險。
7.【強制】定義 DO/DTO/VO 等 POJO 類時,不要設定任何屬性默認值。
反例:POJO 類的 createTime 默認值為 new Date(),但是這個屬性在數據提取時并沒有置入具體值,在
更新其它字段時又附帶更新了此字段,導致創建時間被修改成當前時間。
8.【強制】構造方法里面禁止加入任何業務邏輯,如果有初始化邏輯,請放在 init 方法中。
9.【強制】POJO 類必須寫 toString 方法。使用 IDE 中的工具:source> generate toString
時,如果繼承了另一個 POJO 類,注意在前面加一下 super.toString。
說明:在方法執行拋出異常時,可以直接調用 POJO 的 toString()方法打印其屬性值,便于排查問題。
10.【推薦】使用索引訪問用 String 的 split 方法得到的數組時,需做最后一個分隔符后有無內容
的檢查,否則會有拋 IndexOutOfBoundsException 的風險。
說明:
String str = “a,b,c,”;
String[] ary = str.split(“,”);
// 預期大于 3,結果是 3
System.out.println(ary.length);
11.【推薦】當一個類有多個構造方法,或者多個同名方法,這些方法應該按順序放置在一起,便
于閱讀,此條規則優先于下一條。
12.【推薦】 類內方法定義的順序依次是:公有方法或保護方法 > 私有方法 > getter / setter
方法。
說明:公有方法是類的調用者和維護者最關心的方法,首屏展示最好;保護方法雖然只是子類關心,也可
能是“模板設計模式”下的核心方法;而私有方法外部一般不需要特別關心,是一個黑盒實現;因為承載
的信息價值較低,所有 Service 和 DAO 的 getter/setter 方法放在類體最后。
13.【推薦】setter 方法中,參數名稱與類成員變量名稱一致,this.成員名 = 參數名。在
getter/setter 方法中,不要增加業務邏輯,增加排查問題的難度。
反例:
public Integer getData () {
if (condition) {
return this.data + 100; } else {
return this.data - 100; } }
日期時間
1.【強制】日期格式化時,傳入 pattern 中表示年份統一使用小寫的 y。
說明:日期格式化時,yyyy 表示當天所在的年,而大寫的 YYYY 代表是 week in which year(JDK7 之后
引入的概念),意思是當天所在的周屬于的年份,一周從周日開始,周六結束,只要本周跨年,返回的 YYYY
就是下一年。
正例:表示日期和時間的格式如下所示:
new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”)
2.【強制】在日期格式中分清楚大寫的 M 和小寫的 m,大寫的 H 和小寫的 h 分別指代的意義。
說明:日期格式中的這兩對字母表意如下:
1) 表示月份是大寫的 M; 2) 表示分鐘則是小寫的 m; 3) 24 小時制的是大寫的 H; 4) 12 小時制的則是小寫的 h。
3.【強制】獲取當前毫秒數:System.currentTimeMillis(); 而不是 new Date().getTime()。
說明:如果想獲取更加精確的納秒級時間值,使用 System.nanoTime 的方式。在 JDK8 中,針對統計時間
等場景,推薦使用 Instant 類。
4.【推薦】使用枚舉值來指代月份。如果使用數字,注意 Date,Calendar 等日期相關類的月份
month 取值在 0-11 之間。
說明:參考 JDK 原生注釋,Month value is 0-based. e.g., 0 for January.
正例: Calendar.JANUARY,Calendar.FEBRUARY,Calendar.MARCH 等來指代相應月份來進行傳參或
比較。
集合處理
1.【強制】在使用 java.util.stream.Collectors 類的 toMap()方法轉為 Map 集合時,一定要使
用含有參數類型為 BinaryOperator,參數名為 mergeFunction 的方法,否則當出現相同 key
值時會拋出 IllegalStateException 異常。
說明:參數 mergeFunction 的作用是當出現 key 重復時,自定義對 value 的處理策略。
正例:
List<Pair<String, Double>> pairArrayList = new ArrayList<>(3);
pairArrayList.add(new Pair<>(“version”, 12.10));
pairArrayList.add(new Pair<>(“version”, 12.19));
pairArrayList.add(new Pair<>(“version”, 6.28));
Map<String, Double> map = pairArrayList.stream().collect(
// 生成的 map 集合中只有一個鍵值對:{version=6.28}
Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));
反例:
String[] departments = new String[] {“iERP”, “iERP”, “EIBU”};
// 拋出 IllegalStateException 異常
Map<Integer, String> map = Arrays.stream(departments) .collect(Collectors.toMap(String::hashCode, str -> str));
2.【強制】在使用 java.util.stream.Collectors 類的 toMap()方法轉為 Map 集合時,一定要注
意當 value 為 null 時會拋 NPE 異常。
說明:在 java.util.HashMap 的 merge 方法里會進行如下的判斷:
if (value == null || remappingFunction == null)
throw new NullPointerException();
反例:
List<Pair<String, Double>> pairArrayList = new ArrayList<>(2);
pairArrayList.add(new Pair<>(“version1”, 8.3));
pairArrayList.add(new Pair<>(“version2”, null));
Map<String, Double> map = pairArrayList.stream().collect(
// 拋出 NullPointerException 異常
Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));
3.【強制】使用集合轉數組的方法,必須使用集合的 toArray(T[] array),傳入的是類型完全一
致、長度為 0 的空數組。
反例:直接使用 toArray 無參方法存在問題,此方法返回值只能是 Object[]類,若強轉其它類型數組將出現
ClassCastException 錯誤。
正例:
List list = new ArrayList<>(2);
list.add(“guan”);
list.add(“bao”);
String[] array = list.toArray(new String[0]);
4.【強制】在使用 Collection 接口任何實現類的 addAll()方法時,都要對輸入的集合參數進行
NPE 判斷。
說明:在 ArrayList#addAll 方法的第一行代碼即 Object[] a = c.toArray(); 其中 c 為輸入集合參數,如果
為 null,則直接拋出異常。
5.【強制】使用工具類 Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方法,
它的 add/remove/clear 方法會拋出 UnsupportedOperationException 異常。
說明:asList 的返回對象是一個 Arrays 內部類ArrayList,繼承了AbstractList,它的默認add、remove等方法會拋出UnsupportedOperationException。Arrays.asList 體現的是適配
器模式,只是轉換接口,后臺的數據仍是數組。
String[] str = new String[] { “chen”, “yang”, “hao” };
List list = Arrays.asList(str);
第一種情況:list.add(“yangguanbao”); 運行時異常UnsupportedOperationException
第二種情況:str[0] = “change”; 修改了數組,list也會隨之修改,反之亦然。
6.【推薦】集合初始化時,指定集合初始值大小。
說明:HashMap 使用 HashMap(int initialCapacity) 初始化,如果暫時無法確定集合大小,那么指定默
認值(16)即可。
正例:initialCapacity = (需要存儲的元素個數 / 負載因子) + 1。注意負載因子(即 loader factor)默認
為 0.75,如果暫時無法確定初始值大小,請設置為 16(即默認值)。
反例: HashMap 需要放置 1024 個元素,由于沒有設置容量初始大小,隨著元素增加而被迫不斷擴容,
resize()方法總共會調用 8 次,反復重建哈希表和數據遷移。當放置的集合元素個數達千萬級時會影響程序
性能。
7.【推薦】使用 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 值組合集合。
8.【推薦】高度注意 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 異常。
并發處理
1.【強制】創建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。
正例:自定義線程工廠,并且根據外部特征進行分組,比如,來自同一機房的調用,把機房編號賦值給
2.【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。
說明:線程池的好處是減少在創建和銷毀線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題。
如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。
3.【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這
樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
說明:Executors 返回的線程池對象的弊端如下: 1) FixedThreadPool 和 SingleThreadPool:
允許的請求隊列長度為 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。 2) CachedThreadPool:
允許的創建線程數量為 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。
4.【強制】SimpleDateFormat 是線程不安全的類,一般不要定義為 static 變量,如果定義為 static,
必須加鎖,或者使用 DateUtils 工具類。
正例:注意線程安全,使用 DateUtils。亦推薦如下處理:
說明:如果是 JDK8 的應用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,
DateTimeFormatter 代替 SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable
thread-safe。
5.【強制】必須回收自定義的 ThreadLocal 變量,尤其在線程池場景下,線程經常會被復用,
如果不清理自定義的 ThreadLocal 變量,可能會影響后續業務邏輯和造成內存泄露等問題。
盡量在代理中使用 try-finally 塊進行回收。
正例:
6.【強制】高并發時,同步調用應該去考量鎖的性能損耗。能用無鎖數據結構,就不要用鎖;能
鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。
說明:盡可能使加鎖的代碼塊工作量盡可能的小,避免在鎖代碼塊中調用 RPC 方法。
7.【強制】在使用阻塞等待獲取鎖的方式中,必須在 try 代碼塊之外,并且在加鎖方法與 try 代
碼塊之間沒有任何可能拋出異常的方法調用,避免加鎖成功后,在 finally 中無法解鎖。
說明一:如果在 lock 方法與 try 代碼塊之間的方法調用拋出異常,那么無法解鎖,造成其它線程無法成功
獲取鎖。
說明二:如果 lock 方法在 try 代碼塊之內,可能由于其它方法拋出異常,導致在 finally 代碼塊中,unlock
對未加鎖的對象解鎖,它會調用 AQS 的 tryRelease 方法(取決于具體實現類),拋出
IllegalMonitorStateException 異常。
說明三:在 Lock 對象的 lock 方法實現中可能拋出 unchecked 異常,產生的后果與說明二相同。
8.【強制】在使用嘗試機制來獲取鎖的方式中,進入業務代碼塊之前,必須先判斷當前線程是否
持有鎖。鎖的釋放規則與鎖的阻塞等待方式相同。
說明:Lock 對象的 unlock 方法在執行時,它會調用 AQS 的 tryRelease 方法(取決于具體實現類),如果
當前線程不持有鎖,則拋出 IllegalMonitorStateException 異常。
正例:
9.【強制】并發修改同一記錄時,避免更新丟失,需要加鎖。要么在應用層加鎖,要么在緩存加
鎖,要么在數據庫層使用樂觀鎖,使用 version 作為更新依據。
說明:如果每次訪問沖突概率小于 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數不得小于
3 次。
10.【推薦】資金相關的金融敏感信息,使用悲觀鎖策略。
說明:樂觀鎖在獲得鎖的同時已經完成了更新操作,校驗邏輯容易出現漏洞,另外,樂觀鎖對沖突的解決策
略有較復雜的要求,處理不當容易造成系統壓力或數據異常,所以資金相關的金融敏感信息不建議使用樂觀
鎖更新。
正例:悲觀鎖遵循一鎖、二判、三更新、四釋放的原則。
11.【推薦】避免 Random 實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一 seed
導致的性能下降。
說明:Random 實例包括 java.util.Random 的實例或者 Math.random()的方式。
正例:在 JDK7 之后,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前,需要編碼保證每個線
程持有一個單獨的 Random 實例。
控制語句
1.【強制】當 switch 括號內的變量類型為 String 并且此變量為外部參數時,必須先進行 null
判斷。
2.【強制】三目運算符 condition? 表達式 1 : 表達式 2 中,高度注意表達式 1 和 2 在類型對齊
時,可能拋出因自動拆箱導致的 NPE 異常。
說明:以下兩種場景會觸發類型對齊的拆箱操作:
1) 表達式 1 或表達式 2 的值只要有一個是原始類型。
2) 表達式 1 或表達式 2 的值的類型不一致,會強制拆箱升級成表示范圍更大的那個類型。
反例:
Integer a = 1;
Integer b = 2;
Integer c = null;
Boolean flag = false;
// ab 的結果是 int 類型,那么 c 會強制拆箱成 int 類型,拋出 NPE 異常
Integer result=(flag? ab : c);
3.【推薦】當某個方法的代碼總行數超過 10 行時,return / throw 等中斷邏輯的右大括號后均
需要加一個空行。
說明:這樣做邏輯清晰,有利于代碼閱讀時重點關注。
4.【推薦】表達異常的分支時,少用 if-else 方式,這種方式可以改寫成:
if (condition) {
…
return obj; }
// 接著寫 else 的業務邏輯代碼;
說明:如果非使用 if()…else if()…else…方式表達邏輯(也就是使用上述的表達異常的分支的方式時),避免后續代碼維護困難,請勿超過 3 層。
正例:超過 3 層的 if-else 的邏輯判斷代碼可以使用衛語句、策略模式、狀態模式等來實現,其中衛語句
衛語句即代碼邏輯先考慮失敗、異常、中斷、退出等直接返回的情況,以方法多個出口的方式,解決代碼中判斷分支嵌套的問題,這是逆向思維的體現
示例如下:
public void findBoyfriend (Man man) {if (man.isUgly()) {System.out.println("本姑娘是外貌協會的資深會員");return; }if (man.isPoor()) {System.out.println("貧賤夫妻百事哀");return; }if (man.isBadTemper()) {System.out.println("銀河有多遠,你就給我滾多遠");return; }System.out.println("可以先交往一段時間看看");}5.【推薦】除常用方法(如 getXxx/isXxx)等外,不要在條件判斷中執行其它復雜的語句,將復
雜邏輯判斷的結果賦值給一個有意義的布爾變量名,以提高可讀性。
說明:很多 if 語句內的邏輯表達式相當復雜,與、或、取反混合運算,甚至各種方法縱深調用,理解成本
非常高。如果賦值一個非常好理解的布爾變量名字,則是件令人爽心悅目的事情。
注釋規約
1.【強制】類、類屬性、類方法的注釋必須使用 Javadoc 規范,使用/內容/格式,不得使用
// xxx 方式。
說明:在 IDE 編輯窗口中,Javadoc 方式會提示相關注釋,生成 Javadoc 可以正確輸出相應注釋
2.【強制】所有的抽象方法(包括接口中的方法)必須要用 Javadoc 注釋、除了返回值、參數、
異常說明外,還必須指出該方法做什么事情,實現什么功能。
說明:對子類的實現要求,或者調用注意事項,請一并說明。
3.【強制】方法內部單行注釋,在被注釋語句上方另起一行,使用//注釋。方法內部多行注釋使
用/ */注釋,注意與代碼對齊。
4.【參考】特殊注釋標記
1) 錯誤,不能工作(FIXME):(標記人,標記時間,[預計處理時間])
在注釋中用 FIXME 標記某代碼是錯誤的,而且不能工作,需要及時糾正的情況。
前后端規約
1.【強制】對于需要使用超大整數的場景,服務端一律使用 String 字符串類型返回,禁止使用
Long 類型。
說明:Java 服務端如果直接返回 Long 整型數據給前端,JS 會自動轉換為 Number 類型(注:此類型為雙
精度浮點數,表示原理與取值范圍等同于 Java 中的 Double)。Long 類型能表示的最大值是 2 的 63 次方
-1,在取值范圍之內,超過 2 的 53 次方 (9007199254740992)的數值轉化為 JS 的 Number 時,有些數
值會有精度損失。擴展說明,在 Long 取值范圍內,任何 2 的指數次整數都是絕對不會存在精度損失的,所
以說精度損失是一個概率問題。若浮點數尾數位與指數位空間不限,則可以精確表示任何整數,但很不幸,
雙精度浮點數的尾數位只有 52 位。
反例:通常在訂單號或交易號大于等于 16 位,大概率會出現前后端單據不一致的情況,比如,“orderId”:
362909601374617692,前端拿到的值卻是: 362909601374617660
其他
1.【強制】注意 Math.random() 這個方法返回是 double 類型,注意取值的范圍 0≤x<1(能夠
取到零值,注意除零異常),如果想獲取整數類型的隨機數,不要將 x 放大 10 的若干倍然后
取整,直接使用 Random 對象的 nextInt 或者 nextLong 方法。
異常
1.【強制】錯誤碼為字符串類型,共 5 位,分成兩個部分:錯誤產生來源+四位數字編號。
說明:錯誤產生來源分為 A/B/C,A 表示錯誤來源于用戶,比如參數錯誤,用戶安裝版本過低,用戶支付
超時等問題;B 表示錯誤來源于當前系統,往往是業務邏輯出錯,或程序健壯性差等問題;C 表示錯誤來源
于第三方服務,比如 CDN 服務出錯,消息投遞超時等問題;四位數字編號從 0001 到 9999,大類之間的
步長間距預留 100
2.【強制】異常捕獲后不要用來做流程控制,條件控制。
說明:異常設計的初衷是解決程序運行中的各種意外情況,且異常的處理效率比條件判斷方式要低很多
3.【強制】catch 時請分清穩定代碼和非穩定代碼,穩定代碼指的是無論如何不會出錯的代碼。
對于非穩定代碼的 catch 盡可能進行區分異常類型,再做對應的異常處理。
說明:對大段代碼進行 try-catch,使程序無法根據不同的異常做出正確的應激反應,也不利于定位問題,
這是一種不負責任的表現。
正例:用戶注冊的場景中,如果用戶輸入非法字符,或用戶名稱已存在,或用戶輸入密碼過于簡單,在程
序上作出分門別類的判斷,并提示給用戶
4.【強制】在調用 RPC、二方包、或動態生成類的相關方法時,捕捉異常必須使用 Throwable
類來進行攔截。
說明:通過反射機制來調用方法,如果找不到方法,拋出 NoSuchMethodException。什么情況會拋出
NoSuchMethodError 呢?二方包在類沖突時,仲裁機制可能導致引入非預期的版本使類的方法簽名不匹配,
或者在字節碼修改框架(比如:ASM)動態創建或修改類時,修改了相應的方法簽名。這些情況,即使代
碼編譯期是正確的,但在代碼運行期時,會拋出 NoSuchMethodError。
5.【強制】在日志輸出時,字符串變量之間的拼接使用占位符的方式。
說明:因為 String 字符串的拼接會使用 StringBuilder 的 append()方式,有一定的性能損耗。使用占位符僅
是替換動作,可以有效提升性能。
正例:logger.debug(“Processing trade with id: {} and symbol: {}”, id, symbol);
6.【強制】生產環境禁止直接使用 System.out 或 System.err 輸出日志或使用
e.printStackTrace()打印異常堆棧。
說明:標準日志輸出與標準錯誤輸出文件每次 Jboss 重啟時才滾動,如果大量輸出送往這兩個文件,容易
造成文件大小超過操作系統大小限制。
7.【強制】異常信息應該包括兩類信息:案發現場信息和異常堆棧信息。如果不處理,那么通過
關鍵字 throws 往上拋出。
正例:logger.error(“inputParams:{} and errorMessage:{}”, 各類參數或者對象 toString(), e.getMessage(), e);
8.【強制】日志打印時禁止直接用 JSON 工具將對象轉換成 String。
說明:如果對象里某些 get 方法被覆寫,存在拋出異常的情況,則可能會因為打印日志而影響正常業務流
程的執行。
正例:打印日志時僅打印出業務相關屬性值或者調用其對象的 toString()方法。
單元測試
1.【強制】單元測試應該是全自動執行的,并且非交互式的。測試用例通常是被定期執行的,執
行過程必須完全自動化才有意義。輸出結果需要人工檢查的測試不是一個好的單元測試。單元
測試中不準使用 System.out 來進行人肉驗證,必須使用 assert 來驗證
2.【強制】保持單元測試的獨立性。為了保證單元測試穩定可靠且便于維護,單元測試用例之間
決不能互相調用,也不能依賴執行的先后次序。
反例:method2 需要依賴 method1 的執行,將執行結果作為 method2 的輸入。
3.【強制】對于單元測試,要保證測試粒度足夠小,有助于精確定位問題。單測粒度至多是類級
別,一般是方法級別。
說明:只有測試粒度小才能在出錯時盡快定位到出錯位置。單測不負責檢查跨類或者跨系統的交互邏輯,
那是集成測試的領域。
4.【推薦】單元測試的基本目標:語句覆蓋率達到 70%;核心模塊的語句覆蓋率和分支覆蓋率都
要達到 100%
說明:在工程規約的應用分層中提到的 DAO 層,Manager 層,可重用度高的 Service,都應該進行單元測
試
5.【推薦】和數據庫相關的單元測試,可以設定自動回滾機制,不給數據庫造成臟數據。或者對
單元測試產生的數據有明確的前后綴標識。
正例:在阿里巴巴企業智能事業部的內部單元測試中,使用 ENTERPRISE_INTELLIGENCE UNIT_TEST
的前綴來標識單元測試相關代碼
6.【推薦】對于數據庫相關的查詢,更新,刪除等操作,不能假設數據庫里的數據是存在的,或
者直接操作數據庫把數據插入進去,請使用程序插入或者導入數據的方式來準備數據。
反例:刪除某一行數據的單元測試,在數據庫中,先直接手動增加一行作為刪除目標,但是這一行新增數
據并不符合業務插入規則,導致測試結果異常。
7.【推薦】單元測試作為一種質量保障手段,在項目提測前完成單元測試,不建議項目發布后補
充單元測試用例
MySQL 數據庫
1.【強制】表達是與否概念的字段,必須使用 is_xxx 的方式命名,數據類型是 unsigned tinyint
(1 表示是,0 表示否)。
說明:任何字段如果為非負數,必須是 unsigned。
注意:POJO 類中的任何布爾類型的變量,都不要加 is 前綴,所以,需要在設置從 is_xxx 到
Xxx 的映射關系。數據庫表示是與否的值,使用 tinyint 類型,堅持 is_xxx 的命名方式是為了明確其取值含
義與取值范圍。
正例:表達邏輯刪除的字段名 is_deleted,1 表示刪除,0 表示未刪除。
2.【強制】禁用保留字,如 desc、range、match、delayed 等,請參考 MySQL 官方保留字
3.【強制】主鍵索引名為 pk_字段名;唯一索引名為 uk_字段名;普通索引名則為 idx_字段名。
說明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的簡稱
4.【強制】小數類型為 decimal,禁止使用 float 和 double。
說明:在存儲的時候,float 和 double 都存在精度損失的問題,很可能在比較值的時候,得到不正確的
結果。如果存儲的數據范圍超過 decimal 的范圍,建議將數據拆成整數和小數并分開存儲
5.【強制】varchar 是可變長字符串,不預先分配存儲空間,長度不要超過 5000,如果存儲長度
大于此值,定義字段類型為 text,獨立出來一張表,用主鍵來對應,避免影響其它字段索引效
率。
6.【強制】表必備三字段:id, create_time, update_time。
說明:其中 id 必為主鍵,類型為 bigint unsigned、單表時自增、步長為 1。create_time, update_time
的類型均為 datetime 類型,前者現在時表示主動式創建,后者過去分詞表示被動式更新
7.【推薦】字段允許適當冗余,以提高查詢性能,但必須考慮數據一致。冗余字段應遵循:
1) 不是頻繁修改的字段。
2) 不是唯一索引的字段。
3) 不是 varchar 超長字段,更不能是 text 字段。
正例:各業務線經常冗余存儲商品名稱,避免查詢時需要調用 IC 服務獲取。
8.【推薦】單表行數超過 500 萬行或者單表容量超過 2GB,才推薦進行分庫分表。
說明:如果預計三年后的數據量根本達不到這個級別,請不要在創建表時就分庫分表
9.【強制】業務上具有唯一特性的字段,即使是組合字段,也必須建成唯一索引。
說明:不要以為唯一索引影響了 insert 速度,這個速度損耗可以忽略,但提高查找速度是明顯的;另外,
即使在應用層做了非常完善的校驗控制,只要沒有唯一索引,根據墨菲定律,必然有臟數據產生。
10.【強制】超過三個表禁止 join。需要 join 的字段,數據類型保持絕對一致;多表關聯查詢時,
保證被關聯的字段需要有索引。
說明:即使雙表 join 也要注意表索引、SQL 性能
11.【強制】在 varchar 字段上建立索引時,必須指定索引長度,沒必要對全字段建立索引,根據
實際文本區分度決定索引長度
說明:索引的長度與區分度是一對矛盾體,一般對字符串類型數據,長度為 20 的索引,區分度會高達 90%
以上,可以使用 count(distinct left(列名, 索引長度))/count()的區分度來確定
12.【推薦】如果有 order by 的場景,請注意利用索引的有序性。order by 最后的字段是組合索
引的一部分,并且放在索引組合順序的最后,避免出現 file_sort 的情況,影響查詢性能。
正例:where a=? and b=? order by c; 索引:a_b_c
反例:索引如果存在范圍查詢,那么索引有序性無法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 無
法排序
13.【推薦】利用延遲關聯或者子查詢優化超多分頁場景。
說明:MySQL 并不是跳過 offset 行,而是取 offset+N 行,然后返回放棄前 offset 行,返回 N 行,那當
offset 特別大的時候,效率就非常的低下,要么控制返回的總頁數,要么對超過特定閾值的頁數進行 SQL
改寫。
正例:先快速定位需要獲取的 id 段,然后再關聯:
SELECT t1. FROM 表 1 as t1, (select id from 表 1 where 條件 LIMIT 100000,20 ) as t2 where t1.id=t2.id
14.【推薦】建組合索引的時候,區分度最高的在最左邊。
正例:如果 where a=? and b=?,a 列的幾乎接近于唯一值,那么只需要單建 idx_a 索引即可。
說明:存在非等號和等號混合判斷條件時,在建索引時,請把等號條件的列前置。如:where c>? and d=?
那么即使 c 的區分度更高,也必須把 d 放在索引的最前列,即建立組合索引 idx_d_c。
15.【推薦】建組合索引的時候,區分度最高的在最左邊。
正例:如果 where a=? and b=?,a 列的幾乎接近于唯一值,那么只需要單建 idx_a 索引即可。
說明:存在非等號和等號混合判斷條件時,在建索引時,請把等號條件的列前置。如:where c>? and d=?
那么即使 c 的區分度更高,也必須把 d 放在索引的最前列,即建立組合索引 idx_d_c
16.【推薦】防止因字段類型不同造成的隱式轉換,導致索引失效。
17.【參考】創建索引時避免有如下極端誤解:
1) 索引寧濫勿缺。認為一個查詢就需要建一個索引。
2) 吝嗇索引的創建。認為索引會消耗空間、嚴重拖慢記錄的更新以及行的新增速度。
3) 抵制惟一索引。認為惟一索引一律需要在應用層通過“先查后插”方式解決
17.【強制】使用 ISNULL()來判斷是否為 NULL 值。
說明:NULL 與任何值的直接比較都為 NULL。 1) NULL<>NULL 的返回結果是 NULL,而不是 false。 2) NULL=NULL 的返回結果是 NULL,而不是 true。 3) NULL<>1 的返回結果是 NULL,而不是 true。
反例:在 SQL 語句中,如果在 null 前換行,影響可讀性。select * from table where column1 is null and
column3 is not null; 而ISNULL(column)是一個整體,簡潔易懂。從性能數據上分析,ISNULL(column)
執行效率更快一些。
18.【強制】不得使用外鍵與級聯,一切外鍵概念必須在應用層解決。
說明:(概念解釋)學生表中的 student_id 是主鍵,那么成績表中的 student_id 則為外鍵。如果更新學
生表中的 student_id,同時觸發成績表中的 student_id 更新,即為級聯更新。外鍵與級聯更新適用于單機
低并發,不適合分布式、高并發集群;級聯更新是強阻塞,存在數據庫更新風暴的風險;外鍵影響數據庫
的插入速度。
19.【推薦】SQL 語句中表的別名前加 as,并且以 t1、t2、t3、…的順序依次命名。
說明:1)別名可以是表的簡稱,或者是依照表在 SQL 語句中出現的順序,以 t1、t2、t3 的方式命名。2)
別名前加 as 使別名更容易識別。
正例:select t1.name from table_first as t1, table_second as t2 where t1.id=t2.id;
20.【推薦】in 操作能避免則避免,若實在避免不了,需要仔細評估 in 后邊的集合元素數量,控
制在 1000 個之內。
21.【參考】因國際化需要,所有的字符存儲與表示,均采用 utf8 字符集,那么字符計數方法需 要注意。
說明:
SELECT LENGTH(“輕松工作”); 返回為 12
SELECT CHARACTER_LENGTH(“輕松工作”); 返回為 4
如果需要存儲表情,那么選擇 utf8mb4 來進行存儲,注意它與 utf8 編碼的區別。
22.【參考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系統和事務日志資源少,但 TRUNCATE
無事務且不觸發 trigger,有可能造成事故,故不建議在開發代碼中使用此語句。
說明:TRUNCATE TABLE 在功能上與不帶 WHERE 子句的 DELETE 語句相同。
ORM映射
1.【強制】在表查詢中,一律不要使用 * 作為查詢的字段列表,需要哪些字段必須明確寫明。
說明:1)增加查詢分析器解析成本。2)增減字段容易與 resultMap 配置不一致。3)無用字段增加網絡
消耗,尤其是 text 類型的字段。
2.【強制】POJO 類的布爾屬性不能加 is,而數據庫字段必須加 is_,要求在 resultMap 中進行
字段與屬性之間的映射。
說明:參見定義 POJO 類以及數據庫字段定義規定,在 sql.xml 增加映射,是必須的
3.【強制】不要用 resultClass 當返回參數,即使所有類屬性名與數據庫字段一一對應,也需要
定義;反過來,每一個表也必然有一個與之對應。
說明:配置映射關系,使字段與 DO 類解耦,方便維護。
4.【強制】不允許直接拿 HashMap 與 Hashtable 作為查詢結果集的輸出。
反例:某同學為避免寫一個xxx,直接使用 HashTable 來接收數據庫返回結
果,結果出現日常是把 bigint 轉成 Long 值,而線上由于數據庫版本不一樣,解析成 BigInteger,導致線
上問題
5.【強制】更新數據表記錄時,必須同時更新記錄對應的 update_time 字段值為當前時間。
工程結構
1.【推薦】根據業務架構實踐,結合業界分層規范與流行技術框架分析,推薦分層結構如圖所示,
默認上層依賴于下層,箭頭關系表示可直接依賴,如:開放 API 層可以依賴于 Web 層 (Controller 層),也可以直接依賴于 Service 層,依此類推:
? 開放 API 層:可直接封裝 Service 接口暴露成 RPC 接口;通過 Web 封裝成 http 接口;網關控制層等。
? 終端顯示層:各個端的模板渲染并執行顯示的層。當前主要是 velocity 渲染,JS 渲染,JSP 渲染,移
動端展示等。
? Web 層:主要是對訪問控制進行轉發,各類基本參數校驗,或者不復用的業務簡單處理等。
? Service 層:相對具體的業務邏輯服務層。
? Manager 層:通用業務處理層,它有如下特征:
1) 對第三方平臺封裝的層,預處理返回結果及轉化異常信息,適配上層接口。
2) 對 Service 層通用能力的下沉,如緩存方案、中間件通用處理。
3) 與 DAO 層交互,對多個 DAO 的組合復用。
? DAO 層:數據訪問層,與底層 MySQL、Oracle、Hbase、OB 等進行數據交互。
? 第三方服務:包括其它部門 RPC 服務接口,基礎平臺,其它公司的 HTTP 接口,如淘寶開放平臺、支
付寶付款服務、高德地圖服務等。
? 外部數據接口:外部(應用)數據存儲服務提供的接口,多見于數據遷移場景中。
2.【參考】分層領域模型規約:
? DO(Data Object):此對象與數據庫表結構一一對應,通過 DAO 層向上傳輸數據源對象。
? DTO(Data Transfer Object):數據傳輸對象,Service 或 Manager 向外傳輸的對象。
? BO(Business Object):業務對象,可以由 Service 層輸出的封裝業務邏輯的對象。
? Query:數據查詢對象,各層接收上層的查詢請求。注意超過 2 個參數的查詢封裝,禁止使用 Map 類
來傳輸。 ? VO(View Object):顯示層對象,通常是 Web 向模板渲染引擎層傳輸的對象
3.【強制】二方庫版本號命名方式:主版本號.次版本號.修訂號
1)主版本號:產品方向改變,或者大規模 API 不兼容,或者架構不兼容升級。 2) 次版本號:保持相對兼容性,增加主要功能特性,影響范圍極小的 API 不兼容修改。
3) 修訂號:保持完全兼容性,修復 BUG、新增次要功能特性等。
說明:注意起始版本號必須為:1.0.0,而不是 0.0.1。
反例:倉庫內某二方庫版本號從 1.0.0.0 開始,一直默默“升級”成 1.0.0.64,完全失去版本的語義信息
4.【強制】線上應用不要依賴 SNAPSHOT 版本(安全包除外);正式發布的類庫必須先去中央倉
庫進行查證,使 RELEASE 版本號有延續性,且版本號不允許覆蓋升級。
說明:不依賴 SNAPSHOT 版本是保證應用發布的冪等性。另外,也可以加快編譯時的打包構建
5.【強制】二方庫里可以定義枚舉類型,參數可以使用枚舉類型,但是接口返回值不允許使用枚
舉類型或者包含枚舉類型的 POJO 對象
6.【強制】依賴于一個二方庫群時,必須定義一個統一的版本變量,避免版本號不一致。
說明:依賴 springframework-core,-context,-beans,它們都是同一個版本,可以定義一個變量來保存版
本:${spring.version},定義依賴的時候,引用該版本。
7.【參考】為避免應用二方庫的依賴沖突問題,二方庫發布者應當遵循以下原則:
1)精簡可控原則。移除一切不必要的 API 和依賴,只包含 Service API、必要的領域模型對象、Utils 類、
常量、枚舉等。如果依賴其它二方庫,盡量是 provided 引入,讓二方庫使用者去依賴具體版本號;無 log
具體實現,只依賴日志框架。
2)穩定可追溯原則。每個版本的變化應該被記錄,二方庫由誰維護,源碼在哪里,都需要能方便查到。除
非用戶主動升級版本,否則公共二方庫的行為不應該發生變化。
8.【推薦】高并發服務器建議調小 TCP 協議的 time_wait 超時時間。
說明:操作系統默認 240 秒后,才會關閉處于 time_wait 狀態的連接,在高并發訪問下,服務器端會因為
處于 time_wait 的連接數太多,可能無法建立新的連接,所以需要在服務器上調小此等待值。
正例:在 linux 服務器上請通過變更/etc/sysctl.conf 文件去修改該缺省值(秒):
net.ipv4.tcp_fin_timeout = 30
9.【推薦】調大服務器所支持的最大文件句柄數(File Descriptor,簡寫為 fd)。
說明:主流操作系統的設計是將 TCP/UDP 連接采用與文件一樣的方式去管理,即一個連接對應于一個 fd。
主流的linux服務器默認所支持最大fd數量為1024,當并發連接數很大時很容易因為fd不足而出現“open
too many files”錯誤,導致新的連接無法建立。建議將 linux 服務器所支持的最大句柄數調高數倍(與服
務器的內存數量相關)。
10.【推薦】給 JVM 環境參數設置-XX:+HeapDumpOnOutOfMemoryError 參數,讓 JVM 碰到 OOM
場景時輸出 dump 信息。
說明:OOM 的發生是有概率的,甚至相隔數月才出現一例,出錯時的堆內信息對解決問題非常有幫助
11.【推薦】在線上生產環境,JVM 的 Xms 和 Xmx 設置一樣大小的內存容量,避免在 GC 后調整
堆大小帶來的壓力。
設計規約
1.【強制】存儲方案和底層數據結構的設計獲得評審一致通過,并沉淀成為文檔。
說明:有缺陷的底層數據結構容易導致系統風險上升,可擴展性下降,重構成本也會因歷史數據遷移和系
統平滑過渡而陡然增加,所以,存儲方案和數據結構需要認真地進行設計和評審,生產環境提交執行后,
需要進行 double check。
正例:評審內容包括存儲介質選型、表結構設計能否滿足技術方案、存取性能和存儲空間能否滿足業務發
展、表或字段之間的辯證關系、字段名稱、字段類型、索引等;數據結構變更(如在原有表中新增字段)
也需要進行評審通過后上線
2.【強制】如果某個業務對象的狀態超過 3 個,使用狀態圖來表達并且明確狀態變化的各個觸發
條件。
說明:狀態圖的核心是對象狀態,首先明確對象有多少種狀態,然后明確兩兩狀態之間是否存在直接轉換
關系,再明確觸發狀態轉換的條件是什么。
正例:淘寶訂單狀態有已下單、待付款、已付款、待發貨、已發貨、已收貨等。比如已下單與已收貨這兩
種狀態之間是不可能有直接轉換關系的。
3.【強制】在需求分析階段,如果與系統交互的 User 超過一類并且相關的 User Case 超過 5 個,
使用用例圖來表達更加清晰的結構化需求
4.【強制】如果系統中某個功能的調用鏈路上的涉及對象超過 3 個,使用時序圖來表達并且明確
各調用環節的輸入與輸出。
說明:時序圖反映了一系列對象間的交互與協作關系,清晰立體地反映系統的調用縱深鏈路
5.【強制】如果系統中模型類超過 5 個,并且存在復雜的依賴關系,使用類圖來表達并且明確類
之間的關系。
說明:類圖像建筑領域的施工圖,如果搭平房,可能不需要,但如果建造螞蟻 Z 空間大樓,肯定需要詳細
的施工圖。
6.【強制】如果系統中超過 2 個對象之間存在協作關系,并且需要表示復雜的處理流程,使用活
動圖來表示。
說明:活動圖是流程圖的擴展,增加了能夠體現協作關系的對象泳道,支持表示并發等
7.【推薦】需求分析與系統設計在考慮主干功能的同時,需要充分評估異常流程與業務邊界。
反例:用戶在淘寶付款過程中,銀行扣款成功,發送給用戶扣款成功短信,但是支付寶入款時由于斷網演
練產生異常,淘寶訂單頁面依然顯示未付款,導致用戶投訴
8.【推薦】類在設計與實現時要符合單一原則。
說明:單一原則最易理解卻是最難實現的一條規則,隨著系統演進,很多時候,忘記了類設計的初衷
9.【推薦】謹慎使用繼承的方式來進行擴展,優先使用聚合/組合的方式來實現。
說明:不得已使用繼承的話,必須符合里氏代換原則,此原則說父類能夠出現的地方子類一定能夠出現,
比如,“把錢交出來”,錢的子類美元、歐元、人民幣等都可以出現
10.【推薦】系統設計階段,根據依賴倒置原則,盡量依賴抽象類與接口,有利于擴展與維護。
說明:低層次模塊依賴于高層次模塊的抽象,方便系統間的解耦
11.【推薦】系統設計階段,共性業務或公共行為抽取出來公共模塊、公共配置、公共類、公共方
法等,在系統中不出現重復代碼的情況,即 DRY 原則(Don’t Repeat Yourself)。
說明:隨著代碼的重復次數不斷增加,維護成本指數級上升。隨意復制和粘貼代碼,必然會導致代碼的重復,
在維護代碼時,需要修改所有的副本,容易遺漏。必要時抽取共性方法,或者抽象公共類,甚至是組件化。
正例:一個類中有多個 public 方法,都需要進行數行相同的參數校驗操作,這個時候請抽取:
private boolean checkParam(DTO dto) {…}
12.【參考】設計文檔的作用是明確需求、理順邏輯、后期維護,次要目的用于指導編碼。
說明:避免為了設計而設計,系統設計文檔有助于后期的系統維護和重構,所以設計結果需要進行分類歸
檔保存。
13.【參考】可擴展性的本質是找到系統的變化點,并隔離變化點。
說明:世間眾多設計模式其實就是一種設計模式即隔離變化點的模式。
正例:極致擴展性的標志,就是需求的新增,不會在原有代碼交付物上進行任何形式的修改
總結
- 上一篇: 基于vue框架添加侧边栏
- 下一篇: 6.10笔记