【代碼質量】-阿里巴巴java開發手冊(代碼質量提升神器)學習筆記

前言:《阿里巴巴 Java 開發手冊》是阿里巴巴集團技術團隊的集體智慧結晶和經驗總結,有了這些前人總結的經驗,可以幫助我們寫出高質量的代碼,同時可以減少Bug數量,少踩坑,提高代碼的可讀性和易維護性。本篇僅作爲學習筆記,總結和提煉一些阿里巴巴java開發手冊中需要遵守的規則,同時也是爲了加深印象,手打了一遍規約,通過此番學習,感覺收穫頗多,非常值得一學。


目錄

1.編程規約

1.1命名風格

1.2常量定義

1.3代碼格式

1.4OOP規約

1.5集合處理

1.6併發處理

1.7控制語句

1.8註釋規約

1.9其它

2.異常日誌

2.1異常處理

2.2日誌規約

3.單元測試

4.安全規約

5.MSQL數據庫

5.1建表規約

5.2索引規約

5.3SQL語句

5.4ORM映射

6.工程結構

6.1應用分層

6.2二方庫依賴

7.設計規約

附錄:專有名詞解釋


 

1.編程規約

1.1命名風格

①【強制】在代碼中不要用下劃線或美元作爲命名的起始或結束符號,例如:_name,$name,name_,name$_等。

②【強制】代碼中嚴禁使用拼音與中英文混合命名,更不允許直接使用中文命名,例如DaZhePromotion (打折),一定要使用拼音,也是在國際通用名的情況下,比如地名:HangZhou,專用名:Alibaba等。

③ 【強制】類名使用大駝峯命名,例如:BaseController,DO / BO / DTO / VO / AO / PO / UID 等除外。

④【強制】方法,參數,局部變量,成員變量都統一使用小駝峯命名,例如:salePrice,getUserInfo()。

⑤【強制】常量命名全部使用大寫,單詞之間用下劃線_隔開,力求語義清晰,不要怕長,例如MAX_COUNT。

⑥【強制】抽象類命名要以Abstract或Base開頭;異常類命名要以Exception結尾,測試類要以類名開始,以Test結尾。

⑦【強制】類型與中括號緊挨相連表示數組,例如:int[] arrayDemo.

⑧【強制】在POJO類中,布爾值變量不要加is,否則可能引起一些RPC框架解析異常,反例:isDeleted。

⑨【強制】包名統一使用小寫單數命名,單詞之間使用點.分隔符隔開。例如:com.alibaba.ai.util,一般推薦採用公司域名後綴+公司名縮寫+所在BU+具體的封裝對象類型進行命名。

⑩【強制】杜絕完全不規範的縮寫,避免望文不能知義。反例:AbstractClass寫成AbsClass,Condition寫成condi。

⑪【推薦】爲了達到代碼自釋義的效果,儘量使用完整的單詞組合來命名,例如JDK的原子更新類名:         AtomicReferenceFieldUpdater,避免使用int a這種形式的命名。

⑫【推薦】如果模塊,接口,類,方法中使用了設計模式,應該在命名時體現出來,例如:LoginProxy,ResourceObserver。

⑬【推薦】接口中的方法和屬性儘量不要加任何修飾符,接口儘量避免定義變量,一定要定義要定義與接口相關的變量,反例:

public abstract void f()。

⑭【推薦】接口和實現類的命名有兩套規則,接口實現類的命名結尾推薦使用Impl。

⑮【參考】枚舉命名結尾儘量以Enum結尾,枚舉中的成員名稱統一使用大寫,單詞直接用下劃線隔開。

⑯【參考】各層命名規約:

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/VO/BO/DTO的統稱,禁止命名成xxxPOJO。

1.2常量定義

①【強制】不允許任何魔法值(未經定義的常量)出現在代碼中。反例:

 String key = "id#taobao_" + tradeId; map.put(key,value);

②【強制】在long或Long賦值時,以大寫L結尾,避免使用小寫l,容易與數字1混淆。

③【推薦】不要使用一個常量類維護所有常量,要按常量功能進行歸類,分開維護。正例:緩存相關常量放在CacheConsts類 下,系統配置相關常量放在ConfigConsts下。

④【推薦】常量的複用層次共有5層:跨應用共享常量,應用內共享常量,子工程內共享常量,包內共享常量,類內共享常量。

 1)跨應用共享常量:放置在二方庫中,通常是client.jar中constant目錄下。

 2)應用內共享常量:放置在一方庫中,通常是子模塊中的constant目錄下。

 3)子工程內部共享常量:即在當前子工程的constant目錄下。

 4)包內共享常量:即在當前包下單獨的constant目錄下。

 5)類內部共享常量:直接在類內部使用private static final xxx來定義。

⑤【推薦】如果變量值僅在一個固定範圍內變化,推薦使用枚舉來定義。例如:

如果存在名稱之外的延伸屬性就可以使用枚舉來定義,例如下面例子中的數字就是延伸屬性,可以表示一年中的第幾個季節。

public enum SeasonEnum {     
    SPRING(1), 
    SUMMER(2), 
    AUTUMN(3),
    WINTER(4);     
    private int seq;     
    SeasonEnum(int seq){         
        this.seq = seq;     
    } 
} 

1.3代碼格式

①【強制】大括號的使用約定,如果大括號中內容爲空,可以簡潔地寫成{}即可,如果大括號中是非空代碼,則:

 1)左大括號前不換行。

 2)左大括號後換行。

 3)右大括號前換行。

 4)右大括號後有else等代碼則不換行,表示終止的右大括號後必須換行。

②【強制】左小括號和字符之間不出現空格;同樣右小括號和字符之間也不出現空格,但左大括號前需要空格。

③【強制】if/do/while/switch等保留字與括號之間都必須加空格。

④【強制】任何二目、三目運算符前後都必須加一個空格。

⑤【強制】採用四個空格作爲縮進,不能使用Tab進行縮進,如果一定要用Tab進行縮進,必須在編輯器裏設置Tab填充4個空格。

⑥【強制】註釋的雙斜線與註釋內容之間有且僅有一個空格。

⑦【強制】單行字符數限制不超過120個,超過需要換行,換行需要遵循以下原則:

 1)第二行相對第一行縮進4個空格,從第三行開始,不需要再繼續縮進。

 2)運算符與下文一起換行。

 3)方法調用的點.符號與下文一起換行。

 4)方法調用中多個參數需要換行時,在逗號之後換行。

 5)在括號前不要換行。反例如圖:

⑧【強制】方法多個參數之間,多個參數逗號之後必須有一個空格。例如:function(arg1, arg2, arg3...)

⑨【強制】IDE的Text file encoding設置爲UTF-8;IDE的中文間換行符采用Unix格式,不要使用windows格式。

⑩【推薦】單個方法的總行數不超過80行。

⑪【推薦】沒必要增加若干空格來使某一行字符與上一行對應位置的字符對齊。

⑫【推薦】不同邏輯,不同語義,不同業務的代碼之間,插入一行空行來進行分隔,提升代碼可讀性。

1.4OOP規約

①【強制】避免通過一個類的對象引用來訪問此類的靜態變量或靜態方法,無謂增加編譯器的解析成本,直接用類名來訪問即可,這條在較新版本的IDEA裏已經沒啥用了,因爲通過引用調用時編譯器已經自動過濾掉了這些靜態變量和方法。

②【強制】所有的覆寫方法必須加@Override註解,可以避免覆蓋失敗,編譯時期就能發現。

③【強制】相同類型,相同業務含義時才能使用可變參數,避免使用Object。

④【強制】外部正在用的接口或二方庫依賴的接口,不允許修改方法簽名,以免影響接口的調用方。接口過時必須加@Deprecated註解,並清楚地說明新的接口或服務是什麼。

⑤【強制】不能使用過時的類或方法。

⑥【強制】Object的equals方法容易拋空指針,應使用常量或有確定值的對象來調用equals方法,當然更推薦JDK1.7後提供的Objects.equals()方法。

⑦【強制】所有的包裝類之間的比較全部使用equals進行比較,而非==。

⑧關於基本數據類型與包裝數據類型的使用標準如下:

 1)【強制】所有的POJO類屬性必須使用包裝數據類型。

 2)【強制】RPC方法的參數和返回值必須使用包裝數據類型。

 3)【推薦】所有的局部變量使用基本數據類型。

⑨【強制】定義DO/VO/DO/DTO等POJO類時,不要設定任何屬性的默認值。

⑩【強制】序列化新增屬性時,請不要修改SerialVersionUID字段的值,避免反序列化失敗,如果完全不兼容升級,可以修改SerialVersionUID的值,拋出序列化運行異常。

⑪【強制】構造方法裏禁止加入任何業務邏輯,如果有初始化操作時,請放在init方法中。

⑫【強制】Pojo類必須覆寫toString方法,如果繼承自某個類,需要在前面加上super.toString();

⑬【強制】禁止在Pojo類中同時存在屬性xxx的isXxx和getXxx方法,以免框架在調用時不能確定哪一個是被優先調用的。

⑭【推薦】當使用索引訪問String.split方法得到的數組時,需要對最後一個分隔符後的有無內容做判斷,如果最後一個分隔符後沒內容,否則可能導致拋出IndexOutOfBoundsException的風險。

⑮【推薦】當一個類具有多個構造方法或多個同名方法時,這些方法應按順序放置在一起,便於閱讀和維護,此規則優先於第⑯條規則。

⑯【推薦】類內部定義方法的順序依次是:公有方法或保護方法>私有方法>getter/setter方法。

⑰【推薦】setter方法中,參數名稱與類成員名稱要保持一致,this.成員名=參數名,在getter/setter方法中不要增加業務業務邏輯,以免增加問題的排查難度。

⑱【推薦】循環體內的字符串連接使用StringBuilder的append方法進行連接,最後調用toString返回,避免使用String相加這種形式浪費系統資源。

⑲【推薦】final可以修飾類,成員變量,方法,以及本地變量,下列情況使用fInal關鍵字:

 1)不允許被繼承的類,如String。

 2)不允許修改引用的域對象。

 3)不允許被重寫的方法,如POJO類的setter方法。

 4)不允許運行過程中重新複製的局部變量。

 5)避免上下文重複使用一個變量,使用final可以強制重新定義一個變量,方便更好地進行重構。

⑳【推薦】慎用Object.clone克隆對象,Object提供的clone方法默認是淺拷貝實現,如果要深拷貝需要覆寫clone方法遍歷實現。

※【推薦】類成員與訪問方法控制從嚴:

 1)如果不允許外部直接通過new來創建對象,那麼構造方法必須private。

 2)工具類不允許有Public或default構造方法。

 3)類非static成員變量如果要與子類共享時,必須加protected修飾。

 4)類static及非static成員變量並且僅在本類使用,必須加private修飾。

 5)若是static成員變量,考慮是否使用final。

 6)類成員方法只供類內部調用,必須是private。

 7)類成員方法只對繼承類公開,那麼限制爲protected。

這樣做的理由倒不是爲了安全,即便是private修飾的方法,也可以通過反射暴力破解,這樣做主要是爲了在代碼重構等場景下,能夠減少誤操作。一個private修飾的方法我可以在該類中輕易地對其進行改造/刪除,但是public修飾的方法,在我沒搞清楚外部對其的調用關係,是不敢對其進行隨意修改的。

1.5集合處理

①【強制】關於hashCode和equals方法必須遵循以下幾點:

 1)只要重寫equals,就必須重寫hashCode.

 2) 因爲Set存儲的是不重複對象,判斷對象是否重複依據的是hashCode和equals方法,所以Set存儲的對象必須重寫這兩個方法。

 3)Map的鍵也必須重寫這兩個方法。

②【強制】ArrayList的subList的結果不能強轉爲ArrayList,否則會拋出ClassCastException異常,因爲subList返回的對象並非ArrayList,而是ArrayList的內部類(視圖)subList,對subList的任何操作都會最終反映到原List上。

③【強制】在subList的場景要高度注意對原集合元素的增加或刪除都會導致子列表的遍歷/增加/刪除產生ConcurrentModificationException。

④【強制】使用集合轉數組的方法,必須使用集合的toArray(T[] array),傳入的是類型完全一樣的數組,大小就是list.size()。

⑤【強制】使用工具類Arrays.asList()把數組轉換成集合時,不能使用其修改集合的相關方法,add/remove/clear方法均會拋出unspportedOperationException,因爲該方法僅是適配器模式的體現,底層調用的還是原數組。

⑥【強制】泛型通配符<? extends T>來接收返回數據,此寫法的數據不能使用add()方法,而<? super T>不能使用get方法。

這裏可能不太好記,記住PECS原則就好了:即producer extends,Consumer super。

頻繁往外讀取內容的,有返回值,是生產者,適合用<? extends T>。第二、經常往裏插入的,是消費者,適合用<? super T>。 

⑦【強制】不要在for循環裏對集合元素做移除操作,一定要移除,請使用Iterator迭代器進行操作,否則會拋出ConcurrentModifyException,在併發場景下,需要對Iterator進行加鎖或者考慮使用併發容器。

⑧【強制】在Jdk1.7+,Comparator實現類要滿足下面三個條件,不然ArrayList.sort和Collections.sort會報 IllegalArgumentException。條件如下:

 1) x,y 的比較結果和 y,x 的比較結果相反。  

 2) x>y,y>z,則 x>z。  

 3) x=y,則 x,z 比較結果和 y,z 比較結果相同。 

⑨【推薦】在JDK1.7+,定義集合或泛型時使用Diamond語法,即<>來指代前面已經指定的類型。

⑩【推薦】集合在初始化時需要指定初始值大小。

⑪【推薦】使用EntrySet遍歷Map類集合,而不是keyset方式,後者需要遍歷兩次,在JDK1.8+推薦使用Map.foreach遍歷。

⑫【推薦】高度注意Map類集合的k,v能不能存儲null值的情況,具體如下表格所示:

特別需要注意,受HashMap影響,ConcurrentHashMap會被誤以爲k,v可以爲null,實際上是不可以的,如果爲Null會拋NPE.

⑬【參考】合理利用好集合的有序性(sort)和穩定性(order),避免集合的無序性(unsort)和不穩定性(unorder)帶來的負面影響。

有序性是指遍歷的結果是按照某種比較規則依次排列的,穩定性是指集合每次遍歷的元素次序是一定的。

如ArrayList是unsort/order;HashMap是unsort/unorder;TreeSet是sort/order;

⑭【參考】利用Set元素唯一的特性可以對集合元素快速去重,避免使用List的Contains方法進行遍歷,對比,去重操作。在jdk1.8+可以使用distinct方法進行去重。

1.6併發處理

①【強制】獲取單例對象需要保證線程安全,對象裏的方法也要保證線程安全。

②【強制】創建線程池時請指定有意義的線程名字,方便出錯時回溯。

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

④【強制】不允許在應用中通過Executors去創建線程池,應通過ThreadPoolExecutor的方式,以免出現資源耗盡。

⑤【強制】SimpleDateFormat是線程不安全的類,一般不要定義爲static,定義爲static必須加鎖。

可以使用ThreadLocal封裝或者在JDK1.8+版本推薦使用Instant代替Date,可以使用LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFomat.

⑥【強制】高併發下,同步調用應該去考量鎖的性能損耗。能用無鎖數據結構,就不要用鎖,能鎖區塊,就不要鎖整個方法體,能用對象鎖,就不要用類鎖。

⑦【強制】對多個資源,數據庫表,對象,同時加鎖時,需要注意加鎖的順序要保持一致,否則可能會出現死鎖。

⑧【強制】併發修改同一記錄時,避免更新丟失,需要加鎖。要麼在應用層加鎖,要麼在緩存加鎖,要麼在數據庫加樂觀鎖,使用version作爲更新依據。如果每次訪問衝突效率20%,推薦使用樂觀鎖,否則使用悲觀鎖,樂觀鎖的重試次數不得小於3此。

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

⑩【推薦】使用CountDownLatch進行異步轉操作,每個線程在退出前必須調用countDown方法,線程執行代碼注意Catch異常,確保countDown方法被執行到,避免主線程無法執行到await方法,直到超時才返回結果。

注意:子線程拋出異常堆棧,不能在主線程try-catch到。

⑪【推薦】避免Random實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一個seed導致性能下降。

Random的實例包括java.util.Random的實例或者Math.random()的方式。在JDK1.7+,可以使用ThreadLocalRandom來解決。

⑫【推薦】在併發場景下,通過雙重檢查鎖()實現延遲初始化問題隱患可參考(The "Double-Checked Locking is Broken" Declarationhttps://blog.csdn.net/gerryzhu/article/details/17524281),在jdk1.5+將目標聲明爲volatile類型。

反例:

class LazyInitDemo {   
    private Helper helper = null;  
    public Helper getHelper() {  
    if (helper == null) synchronized(this) { 
        if (helper == null) {        
            helper = new Helper();    
        }      
        return helper;  
    }  // other methods and fields... 

 }

⑬【參考】volatile解決多線程之間內存不可見問題。對於一寫多讀,是可以解決變量同步問題,但如果多寫,同樣無法解決線程安全問題。如果是count++操作,可以採用AtomicInteger來實現,如果是JDK1.8+,推薦使用LongAdder,性能比AtomicLong更好(減少樂觀鎖重試次數)。

⑭【參考】HashMap在容量不夠用進行resize時,由於高併發可能出現死鏈,導致CPU飆升,在開發過程中可以使用其它數據結構或者加鎖來規避此風險。

⑮【參考】ThreadLocal無法解決共享對象的更新問題,ThreadLocal對象建議使用static修飾。這個變量是針對一個線程內所有操作共享的,所以設置爲靜態變量,所有此類實例共享此變量,也就是說類在第一次使用時被裝載,只分配一塊存儲空間,所有此類對象(在這個線程內定義的)都可以操控這個變量。

1.7控制語句

①【強制】在一個switch語句塊中,每個case要麼通過break/return來終止,要麼註釋說明程序將執行到哪一個case爲止,在一個switch塊內,都必須包含一個default語句放在最後,即使空代碼。

②【強制】在if/else/for/while/do語句中,必須使用大括號,哪怕只有一行代碼,避免採用單行編碼的格式:if(xxx) todo;

③【強制】在高併發場景中,避免使用“等於”判斷作爲中斷/退出的條件。如果併發沒有處理好,容易出現等值被擊穿的情況,例如:把庫存等於0時作爲不能售賣的條件,如果併發沒控制好,出現了超賣,庫存變爲負數,就無法終止售賣了。

④【強制】在表達異常的分支時,少用if-else這種形式,可以改寫成如下這種形式:

if (condition) {
    ... return obj;    
}   
// 接着寫 else 的業務邏輯代碼;

如果非得使用If-else方式,【強制】避免代碼維護困難,層級不要超過3級。 超過3層的可以使用衛語句,策略模式,狀態模式。

⑤【推薦】除了常用方法(如getXxx,isXxx等)外,不要在條件判斷中執行其它複雜語句,將複雜邏輯的判斷結果賦值給一個有意義的布爾字段,然後在條件中使用該布爾字段,提高可讀性。

// 僞代碼如下 
final boolean existed = (file.open(fileName, "w") != null) && (...) || (...); 
if (existed) { 
... 
}

⑥【推薦】循環體中的語句要考量性能,以下語句儘量移至循環體外,如定義對象,變量,獲取數據庫連接,進行不必要的try-catch操作。

⑦【推薦】避免採用取反邏輯運算符。取反邏輯不利於快速理解,且取反運算必然存在對應的正向邏輯寫法。

⑧【推薦】接口入參保護,這種場景常見於批量操作的接口對象。例如對於超大批量的操作應該通過衛語句直接返回。

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

 1)調用頻次低的方法。

 2)執行時間開銷很大的方法。此情形中,參數校驗時間幾乎可以忽略不計,但不校驗帶來的錯誤及回退得不償失。

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

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

 5)敏感權限入口。

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

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

 2)底層調用頻度較高的方法。比如Dao層的方法,一般在Controller或者Service層就做了參數校驗了,沒必要再做一遍。

 3)被聲明成private只會被自己代碼所調用的方法,如果可以確定調用方法的入參已經做了校驗,就可以省略。

1.8註釋規約

①【強制】類/類方法/類屬性 必須使用javadoc註解,不得使用//代替/**xx*/。

②【強制】所有的抽象方法(包括接口)都必須使用javadoc註解,除了返回值,參數,異常外還需要指出該方法做什麼事情,實現什麼功能。對子類的實現要求或者調用注意事項,請一併說明。

③【強制】所有的類必須添加創建者和創建時間

④【強制】方法內部單行註釋,使用//並另起一行,多行註釋使用/*xx*/,注意與代碼對齊。

⑤【強制】所有的枚舉類型字段必須有註釋,並說明每個數據項的用途。

⑥【推薦】與其“半吊子”(看笑了)用英文來註釋,不如用中文註釋把問題說清楚。專有名詞和關鍵字用英文即可。

⑦【推薦】代碼修改的同時,註釋也要一起作相應修改。尤其是參數,返回值,異常,核心邏輯等的修改。

⑧【推薦】謹慎註釋掉代碼。在上方詳細地說明,而不是簡單的註釋掉,如果無用,則刪除。

代碼被註釋掉有兩種動機:1)後續會恢復此段代碼的邏輯。2)永久不用。 如果是前者沒有備註很難了解動機,後者則可直接刪除(代碼倉庫裏有歷史記錄)。

⑨【參考】對於註釋的要求:

1)能夠準確反映設計思想和代碼邏輯。

2)能夠描述業務含義。

以便自己日後再看可以快速理解以及代碼繼任者可以快速上手。

⑩【參考】好的命名和代碼結構是可以自解釋的,註釋力求簡潔準確,表達到位,避免出現過度註釋。

⑪【參考】特殊註釋標記,請註明標記人和標記時間。注意及時處理這些標記,線上故障經常會來源於這類標記處的代碼。

1)代辦事宜(TODO):(標記人,標記時間,[預計處理時間])表示需要實現,但目前還未實現的功能。

2)錯誤,不能工作(FIXME):(標記人,標記時間,[預計處理時間])表示代碼是錯誤的,不能工作,需要及時糾正的情況。

1.9其它

①【強制】在使用正則表達式時,利用好正則表達式的預編譯功能,可以有效提高匹配效率。

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

//正確使用姿勢
private static final Pattern pattern = Pattern.compile(regexRule);
 
private void func(...) {
    Matcher m = pattern.matcher(content);
    if (m.matches()) {
        ...
    }
}

②【強制】使用velocity調用POJO類的屬性時,建議直接使用屬性名取值即可,模板引擎會自動按規則進行調用getXxx()如果是布爾基本類型字段,不需要加isXxx,會自動調用isXxx方法。

③【強制】後臺輸送給頁面的變量必須加$!{var}——中間的感嘆號。

說明:如果 var 等於 null 或者不存在,那麼${var}會直接顯示在頁面上。

④【強制】注意Math.random()方法的返回值 0≤x≤1,這個方法可以取到0值,所以要防止除0異常。

⑤【強制】獲取當前毫秒數使用System.currentTimeMillis(),而非new Date().getTime。前者更精確,在JDK1.8+涉及時間的場景,推薦使用Instant。

⑥【推薦】不要在視圖模型中加入任何複雜邏輯。MVC理論,不然分層就沒多大意義了。

⑦【推薦】任何數據結構的構造器或初始化應該指定大小,避免數據結構無限增長喫光內存。

⑧【推薦】及時清理不再使用的代碼段或配置信息。

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

對於後續要恢復的代碼,在註釋代碼的上方,統一規定使用三個斜槓註釋///來說明代碼被註釋的理由。


2.異常日誌

2.1異常處理

①【強制】java類庫中定義的可以通過預檢查方式規避的RuntimeException異常不應該通過catch的方式來處理,比如NPE,IndexOutofBoundsException等。不得不採用catch的異常如字符串轉數字,採用catch來捕獲NumberFormatException。

②【強制】異常不要用來做流程控制,條件控制。因爲異常做流程控制的效率很低。

③【強制】catch時分清穩定代碼和非穩定代碼,穩定代碼是無論如何也不會發生異常的代碼,對於非穩定異常的代碼,catch儘可能區分異常類型,再做對應的異常處理。

對大段代碼try-catch,使程序無法根據對應異常做出正確的應激反應,也不利於問題定位,是不負責任的表現。

④【強制】捕獲異常是爲了處理它,如果捕獲了異常啥也不做,那不如拋給方法的調用者。最外層的業務調用者必須處理異常,將其轉化爲用戶可以理解的內容。

⑤【強制】有try塊放到了事務代碼中,catch異常後,如果需要回滾事務,一定要注意手動回滾事務。

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

⑦【強制】不要在finally塊中使用return,否則將不會再執行try塊中的return。

⑧【強制】捕獲異常與拋出異常必須是一致的,或者捕獲異常是拋出異常的父類。

⑨【推薦】方法的返回值可以爲null,不強制返回空集合或空對象等,必須添加註解說明何種情況下會返回null值。

本手冊明確防止 NPE 是調用者的責任。

⑩【推薦】防止NPE,是程序員的基本修養,注意NPE的產生場景:

1)返回類型爲基本數據類型,return包裝類型數據對象,如果包裝類型數據對象爲null,則會導致自動拆箱時拋NPE。

//反例
public int f() { 
    return Integer;
}

2)數據庫的查詢結果可能爲null。

3)集合裏的元素即使isNotEmpty,取出的元素也可能爲null。

4)遠程調用返回對象時,一律要求進行空指針判斷,防止NPE。

5)對於Session中獲取的數據,建議NPE檢查,避免空指針。

6)級聯調調用obj.getA().getB().getC()...易產生空指針。

建議使用JDK1.8+提供的Optional類來避免NPE。

⑪【推薦】定義時區分checked/unchecked異常,避免直接拋出RuntimeException,更不允許直接拋出Exception或throwable。應使用業務含義自定義異常,推薦業界已經定義過的異常:DAOException,ServiceException。

⑫【參考】對於公司對外開放的http/api開放接口必須使用“錯誤碼”;而應用內部推薦使用異常拋出,跨應用RPC調用方式推薦使用Result方式,封裝isSuccess(),錯誤碼,錯誤簡短信息。

⑬【參考】避免出現重複的代碼DRY((Don’t Repeat Yourself)原則。

2.2日誌規約

①【強制】應用中不允許直接使用日誌系統(Log4j,Logback)中的API,而依賴使用Slf4j中提供的API,使用門面模式的日誌框架,有利於維護各個類的日誌處理方式統一。

②【強制】日誌文件至少保存15天,因爲有些異常具備以“周”爲頻次發生的特點。

③【強制】應用中的擴展日誌(如打點、臨時監控、訪問日誌等)命名方式:

appName_logType_logName.log,通過這種命名可以清楚得看出日誌屬於哪個應用,什麼類型,什麼目的,也有利於歸類查找。正例:mppserver 應用中單獨監控時區轉換異常,如:mppserver_monitor_timeZoneConvert.log 。推薦對日誌進行分類,如將錯誤日誌和業務日誌分開存放,便於查看及日誌系統進行監控。

④【強制】對trace/debug/info級別的日誌輸出,必須使用條件輸出形式或使用佔位符的方式。(這裏發現規約裏一個錯別字,建議打成了建設,截圖爲證,哈哈)

⑤【強制】避免重複打印日誌,浪費磁盤空間,務必在 log4j.xml 中設置 additivity=false。

⑥【強制】異常日誌應該包括兩塊信息:異常的案發現場信息(參數等)和異常堆棧信息。

⑦【推薦】謹慎地記錄日誌,生產環境禁止輸出debug日誌;有選擇地輸出info日誌,如果使用warn來記錄剛上線時的業務行爲信息,一定要注意日誌的量,避免把服務器磁盤撐爆,並記得及時刪除這些觀察日誌。

⑧【推薦】可以使用warn級別日誌來輸出用戶輸入參數錯誤的情況,避免用戶投訴時無所適從。如非必要,請不要在此場景打出error日誌,避免頻繁告警。

⑨【推薦】儘量使用英文來描述日誌錯誤信息,如果日誌中的錯誤信息用英文不能描述清楚的話,可以使用中文來描述,國際化團隊除外。


3.單元測試

①【強制】好的單元測試必須遵循AIR原則。

好的單元測試在線上就好像空氣(AIR)一樣,並不存在,但在測試質量的保障上,卻是非常關鍵的。

好的單元測試具備三個特性-> A:Automatic(自動化) I:Independent(獨立性)R: Repeatable(可重複)

②【強制】單元測試必須是全自動的,而非交互式的。測試用例通常是被定期執行的,執行過程必須完全自動化纔有意義。輸出結構需要人工檢查的不是好的單元測試,不允許使用System.out來人肉驗證,必須使用assert來驗證。

③【強制】保持單元測試的獨立性。爲了保持單元測試穩定可靠且便於維護,單元測試用例之間絕不能相互調用,也不能依賴執行的先後次序。反例:method2 需要依賴 method1 的執行,將執行結果作爲 method2 的輸入。 

④【強制】單元測試是可以重複執行的,不能受到外界環境影響。

⑤【強制】對於單元測試,要保證測試粒度足夠小,有助於精確定位問題。粒度至多是類級別,推薦是方法級。

⑥【強制】核心業務,核心應用,核心模塊的增量代碼確保單元測試通過。

⑦【強制】單元測試代碼必須寫在如下工程目錄下:src/test/java,不允許寫在業務代碼目錄下。

源碼構建會跳過測試目錄,測試框架掃描一般掃描測試目錄。

⑧【推薦】單元測試的基本目標:語句的覆蓋率達到70%+,核心模塊的語句覆蓋率和分支覆蓋率要達到100%。

⑨【推薦】編寫單元測試代碼遵守BCDE原則,以保證被測試模塊的交付質量。

B:Border,邊界值測試,包括循環邊界,特殊取值,特殊時間點,特殊順序等。

C:Corret,正確的輸入,返回正確的結果。

D:Design,與設計文檔結合,來編寫單元測試。

E:Error,強制錯誤信息輸入(如非法數據,異常流程,非業務允許輸入等),並得到預期結果。

⑩【推薦】對於數據庫的相關查詢,更新,刪除操作,不能假設數據庫裏的數據是存在的,或者直接操作數據庫把數據插進去,請使用程序插入或導入數據的方式來準備數據。

⑪【推薦】和數據庫相關的單元測試,可以設定自動回滾機制,確保測完後不留下髒數據。或者對單元測試後的數據留下明顯的前後綴標識。

⑫【推薦】對於不可測試的代碼建議做必要的重構,變成可以測試的代碼。避免爲了達到測試要求,書寫不規範的測試代碼。

⑬【推薦】在設計評審階段,開發人員需要和測試一起確定單元測試範圍,單元測試範圍最好覆蓋所有測試用例。

⑭【推薦】單元測試作爲一種質量保障手段,不建議代碼發佈後補充單元測試用例,建議在項目提測前完成單元測試。

⑮【參考】爲了更方便的進行單元測試,業務代碼應避免以下情況:

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

2)存在過多的全局變量和靜態方法。

3)存在過多的外部依賴。

4)存在過多的條件語句。

⑯【參考】不要對單元測試存在以下誤解:

1)那是測試同學乾的事情。這是開發手冊,這裏面的所有內容都是跟開發強相關的。

2)單元測試代碼是多餘的。系統的整體功能和各單元測試部件的正常是強相關的。

3)單元測試代碼不需要維護。一年半載後,那麼單元測試幾乎處於廢棄狀態。

4)單元測試與線上故障沒有辨證關係。好的單元測試能夠最大限度地規避線上故障。


4.安全規約

①【強制】隸屬於用戶個人的頁面或功能,必須做權限校驗。

②【強制】用戶的敏感信息需要作脫敏處理,不能直接展示。例如手機號:135xxxx5520

③【強制】用戶輸入的SQL參數嚴格使用參數綁定或者使用 METADATA 字段值限定,防止SQL注入。

④【強制】用戶傳入的任何參數必須做有效性驗證。

忽略參數校驗可能會導致:

 1)pageSize過大導致內存溢出

 2)惡意order by導致數據庫查詢變慢

 3)任意重定向

 4)SQL注入

 5)反序列化注入

 6)正則輸入源串拒絕服務ReDoS

⑤【強制】 禁止向HTML頁面輸出未經安全過濾或未經正確轉義的用戶數據。

⑥【強制】表單,AJAX提交必須進行CSRF安全驗證。

⑦【強制】在使用平臺資源,譬如短信,郵件,電話,下單,支付,必須實現正確的防重放機制,如數量限制,疲勞度控制,驗證碼校驗,避免被濫刷而導致資損。

⑧【推薦】發帖,評論,發送即時消息等用戶生成內容的場景,必須實現防刷,文本內容違禁詞過濾等風控策略。


5.MSQL數據庫

5.1建表規約

①【強制】表達是與否概念的字段,必須使用is_xxx的方式命名,字段類型是unsigned tinyint ,1表示是,0表示否。

POJO類中,任何布爾類型字段的變量,都不要加is前綴。

②【強制】表名、字段名必須使用小寫字母或數字,避免使用數字開頭,禁止兩個下劃線之間僅出現數字。數據庫字段名的修改代價很大,因爲無法進行預發佈,所以字段名稱要慎重考慮。

③【強制】表名不適用符數名詞。

④【強制】禁用保留字,如desc,range,match,delay等,詳情參考MYSQL官方保留字。

⑤【強制】主鍵索引名爲pk_字段名;唯一索引命名爲uk_字段名;普通索引命名爲idx_字段名。

⑥【強制】小數類型使用Decimal,禁止使用float或double。

⑦【強制】如果存儲的字符串長度幾乎相等,使用char定常的字符串類型。

⑧【強制】varchar是可變長字符串,不預先分配存儲空間,長度不要超過5000,如果存儲長度大於此值,定義字段類型爲text,獨立出來一張表,用主鍵來對應,避免影響其它字段索引效率。

⑨【強制】表必備三字段:id,gmt_create,gmt_modified

⑩【推薦】表的命名最好是“業務名稱“_"表的作用"。例如:alipay_task。

⑪【推薦】庫名與應用名儘量一致。

⑫【推薦】如果修改字段含義或對字段表示的狀態追加時,需要及時更新字段的註釋。

⑬【推薦】字段允許適當冗餘,以提高查詢性能,但必須考慮數據一致。冗餘字段應該遵循:

1)不是頻繁修改的字段。

2)不是varchar超長字段,更不能是text字段。

⑭【推薦】單表容量超過500萬行或單表容量超過2GB,才推薦進行分庫分表。(如果預計三年後的數據量根本達不到這個級別,請不要在創建表時就分庫分表)

⑮【參考】合適的字符存儲長度,不但節約數據庫表空間,節約索引存儲,更重要的時提升檢索速度。

例如:

5.2索引規約

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

②【強制】超過三個表禁止join,需要join的字段,數據類型必須絕對一致,多表關聯查詢時,保證被關聯的字段需要有索引。

③【強制】在varchar字段上建立索引時,必須指定索引長度,沒必要對全字段建立索引,根據實際文本區分度決定索引長度即可。

索引長度和索引區分度是一對矛盾體,一般對字符串類型數據,索引長度達到20,區分度會達到90%以上,索引區分度可以通過count(distinct left (列名,索引長度)/count(*)來計算。

④【強制】頁面搜索嚴禁左模糊或全模糊,如果一定要這樣請走搜索引擎。

⑤【推薦】如果有order by的場景,請注意利用索引的有序性。order by最後的字段是組合索引的一部分,並且放在索引組合的最後,避免出現file_sort的情況,影響查詢性能。

正例:where a = ?, b = ? order by c; 索引a_b_c 。

反例:where a > ? order by b;索引a_b無法排序。

⑥【推薦】利用覆蓋索引來查詢,避免回表。

覆蓋索引只是一種查詢效果,用explain的結果在extra會出現:using index.

⑦【推薦】利用延遲關聯或子查詢優化超多分頁的場景。

MySQL並不是跳過offset行,而是取offset+N行,然後返回放棄前offset行,返回N行,那麼當offset行特別大時,效率會非常低下,要麼控制返回的頁數,要麼對超過特定閾值的頁數進行SQL改寫。

正例: SELECT a.* FROM 表 1 a, (select id from 表 1 where 條件 LIMIT 100000,20 ) b where a.id=b.id

⑧【推薦】SQL性能優化的目標:至少要達到range級別,要求是ref級別,如果可以是consts最好。

性能:all<index<range<ref<eq_ref<const<system<null

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

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

⑩【推薦】防止因字段類型不同造成的隱式轉換,導致索引失效。

⑪【參考】創建索引時,避免有如下極端誤解:

 1)寧濫勿缺,認爲有一個查詢就需要建一個索引。

 2)寧缺勿濫,認爲索引會佔用存儲空間,嚴重拖慢新增和更新速度。

 3)抵制唯一索引,認爲業務的唯一性一律需要在應用層通過”先查後插“方式解決。

5.3SQL語句

①【強制】不要使用count(列名)或count(常量)代替cout(*),count(*)是SQL92定義的標準統計行數語法,跟數據庫無關,跟NULL和非NULL無關。

②【強制】count(distinct col)計算該列除Null以外的不重複行數,注意count(distinct col1,col2)如果其中有一列全爲NULL,另一列有不同值,返回結果仍爲0。

③【強制】當某一列的值全爲Null時,count(col)返回值爲0,sun(col)返回值爲Null,所以使用sum()要注意NPE問題。

④【強制】使用ISNULL()來判斷是否是Null值。NULL值與任何值的直接比較都是NULL。

⑤【強制】代碼中,寫分頁查詢的邏輯時,若count爲0應直接返回,避免執行後面的分頁語句。

⑥【強制】避免使用外鍵與級聯,一切外鍵概念必須在應用層解決。

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

⑧【強制】數據訂正時(特別是刪除、修改記錄操作)要先select,避免出現誤刪除,確認無誤才能執行更新語句。

⑨【推薦】in操作能避免則避免,若實在無法避免,需要仔細評估in後邊集合元素數量,控制在1000個以內。

⑩【參考】如果有國際化需要,所有的字符存儲與表示,均以utf-8編碼,注意字符統計函數的區別。

如果有需要存儲表情,需要使用utf8mb4,注意它與utf8的區別。

⑪【參考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系統和事務日誌資源少,但 TRUNCATE 無事務且不觸發 trigger,有可能造成事故,故不建議在開發代碼中使用此語句。 

5.4ORM映射

①【強制】在表查詢中,一律不要使用*作爲查詢字段列表,需要哪些字段必須明確寫明。

②【強制】POJO類的布爾屬性不能加is,而數據庫對應字段必須加is_,要求在resultMap中進行字段與屬性之間的映射。

③【強制】不要用resultClass當返回參數,即使所有類屬性名與數據庫字段一一對應,也需要定義;反過來,每一張表也要有一個POJO類與之對應。配置映射關係,使字段與DO類解耦,方便維護。

④【強制】sql.xml配置參數使用#{param},不要使用${param},後者容易SQL注入。

⑤【強制】IBatis自帶的queryForList(String statementName,int start,int size)不推薦使用。

其實現方式是先查詢出statementName對應的所有數據,然後再根據結果取subList,損耗性能。

⑥【強制】不允許直接拿HashMap或HashTable作爲查詢結果集的輸出。

resultClass=”Hashtable”,會置入字段名和屬性值,但是值的類型不可控。 

⑦【強制】更新數據庫表記錄時,必須同步更新記錄對應的gmt_modified字段值爲當前時間。

⑧【推薦】不要寫一個大而全的數據跟新接口。傳入一個POJO類,不管時不是自己目標更新字段,都進行update table col1,col2,col3...這是不對的,執行sql時,不要更新無改動字段,一是容易出錯,二是效率低,三是增加binlog存儲。

⑨【參考】@Transactional事務不要濫用,事務會影響數據庫的QPS,另外使用事務要考慮各方面的回滾方案。包括緩存回滾,搜索引擎回滾,消息補償,統計修正等。

⑩【參考】<isEqual>中的 compareValue 是與屬性值對比的常量,一般是數字,表示相等時帶 上此條件;<isNotEmpty>表示不爲空且不爲 null 時執行;<isNotNull>表示不爲 null 值時 執行。 


6.工程結構

6.1應用分層

①【推薦】圖中默認上層依賴於下層,箭頭關係表示可直接依賴,如開放接口層可直接依賴於web層,也可直接依賴於service層,依此類推:

                                                  

 1)開放接口層:可直接封裝成Service方法暴露成RPC接口;通過Web封裝成Http接口;進行網管安全控制,流量控制等。

 2)終端顯示層:各個端的模板渲染並執行顯示的層,當前主要是velocity渲染、js渲染、jsp渲染、移動端展示等。

 3)Web層:主要是對訪問控制進行轉發,各類基本參數進行校驗,或者不復用的業務簡單處理。

 4)Service層:相對具體的業務邏輯服務層。
 5)Manager層:通用業務處理層,它具有以下特徵:

       A.對第三方平臺封裝的層,預處理返回結果及轉化異常信息。

       B.對Service通用能力的下沉,如緩存方案,中間件通用處理。

       C.與DAO層交互,對多個DAO的組合複用。

 6)外部接口或第三方平臺:包括其它部門開放的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):數據傳輸對象,Manager或Sercice層向外傳輸的對象。

BO(Business Object):業務對象,由Service層輸出的封裝業務邏輯的對象。

AO(Application Object):應用對象,在web層和service層之間抽象的複用對象模型,極爲貼近展示層,複用度不高。

VO(View Object):顯示對象,通常是web層向模板引擎層傳輸的對象。

Query:數據查詢對象,各層接收上層的查詢請求。超過2個參數的查詢封裝,禁止使用Map類來傳輸。

6.2二方庫依賴

①【強制】定義GAV遵循以下規則:

 1)GroupId格式:com.{公司/BU}.業務線[.子業務線],最多4級。

 {公司/BU} 例如alibaba/taobao/tmall等BU一級,子業務線可選。

 正例:com.taobao.jstorm ,com.alibaba.dubbo.register。

②【強制】二方庫版本號命名方式:主版本號.次版本號.修訂號

 1)主版本號:產品方向改變,或者大規模API不兼容,或者架構不兼容升級。

 2)次版本號:保持相對兼容性,增加主要功能特性,影響範圍極小的API不兼容修改。

 3)修訂好:保持完全兼容性,修復bug,新增次要功能特性。

③【強制】線上應用不要依賴SNAPSHOT版本(安全包除外)。

④【強制】二方庫新增或升級,除保持功能點外其它jar包仲裁結果不變。如果有改變,必須明確評估和驗證,建議進行dependency:reslove前後信息對比,如果仲裁結果完全不一致,那麼通過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>裏的依賴都會自動引入,並默認被所有的子項目繼承。

⑨【推薦】二方庫不要有配置項,最低限度不要再增加配置項。

⑩【參考】爲避免應用二方庫的依賴衝突問題,二方庫的發佈者應當遵循以下原則:

 1)精簡可控原則。移除一切不必要的API和依賴,只包含Service API,必要的領域模型對象,utils類,枚舉和常量等。如果依賴其它二方庫,儘量是provided引入,讓二方庫使用者去依賴具體的版本號,無log具體實現,只依賴日誌框架。

 2)穩定可追溯原則。每個版本的變化應該被記錄,二方庫由誰維護,源代碼在哪裏,都需要能方便查到。除非用戶主動升級版本,否則二方庫的行爲不應該發生變化。

6.3服務器

①【推薦】高併發服務器建議調小TCP協議的time_wait超時時間。

操作系統默認240 秒後,纔會關閉處於 time_wait 狀態的連接,在高併發訪問下,服 務器端會因爲處於 time_wait 的連接數太多,可能無法建立新的連接,所以需要在服務器上 調小此等待值。

正例:在 linux 服務器上請通過變更/etc/sysctl.conf 文件去修改該缺省值(秒):net.ipv4.tcp_fin_timeout = 30

②【推薦】調大服務器所支持的最大文件句柄數(FileDescriptor,簡寫爲fd)。

主流操作系統將TCP/UDP連接採用與文件一樣的方式去管理,即一個連接對應一個fd。主流的linux服務器默認最大支持fd數量爲1024,當併發連接數量很大時,容易因爲 fd 不足而出現“open too many files”錯誤,導致新的連接無法建立。

③【推薦】給jvm環境參數: -XX:+HeapDumpOnOutOfMemoryError參數,讓jvm碰到OOM場景時,輸出dump信息。

④【推薦】在線上生產環境,Jvm的最小堆-xmn和最大對-xmx設置一樣大小的內存容量,避免在GC後調整堆大小帶來的壓力。

⑤【參考】服務器內部重定向使用forward;外部重定向使用url拼裝工具類來生成,否則會帶來Url維護不一致的問題和潛在安全風險。


7.設計規約

①【強制】存儲方案和底層數據結構的設計獲得評審一致通過,並沉澱成爲文檔。

有缺陷的底層數據結構容易導致系統風險上升,可擴展性下降,重構成本也會因爲歷史數據遷移和系統平滑過渡而陡然增加,所以存儲方案和數據結構需要認真的設計和評審,生產環境提交執行後需要double check。

②【強制】在需求分析階段,如果與系統交互的User超過一類,並且相關的User Case超過5個,使用用例圖來表示更加清晰的結構化需求。

③【強制】如果某個業務對象的狀態超過3個,使用狀態圖來表達,並且明確狀態變化的各個觸發條件。

狀態圖的核心是對象狀態,首先明確對象有多少種狀態,然後明確兩兩狀態之間是否存在直接轉換關係,再明確觸發狀態轉換的條件是什麼。

正例:淘寶訂單狀態有已下單、待付款、已付款、待發貨、已發貨、已收貨等。比如已下單與已收貨這兩種狀態之間是不可能有直接轉換關係的。

④【強制】如果系統中某個功能上鍊路調用超過3個,使用時序圖來表達且明確各個調用環節的輸入與輸出。

時序圖反映了一系列對象間的交互與協作關係,清晰立體地反映系統的調用縱深鏈路。

⑤【強制】如果系統中的模型類超過5個,並且存在複雜的依賴關係,使用類圖來表達且明確類之間的關係。

類圖像建築領域的施工圖,如果搭平房,可能不需要,但如果建造螞蟻 Z 空間大樓,肯定需要詳細的施工圖。

⑥【強制】如果系統中超過2個對象存在協作關係,並且需要表示複雜的處理流程,需要用活動圖來表示。

活動圖是流程圖的擴展,增加了能夠體現協作關係的對象泳道,支持表示併發等。 

⑦【推薦】需求分析與系統設計需在考慮主幹功能的同時,需要充分評估異常流程與業務邊界。

反例:用戶在淘寶付款過程中,銀行扣款成功,發送給用戶扣款成功短信,但是支付寶入款時由於斷網演練產生異常,淘寶訂單頁面依然顯示未付款,導致用戶投訴。 

⑧【推薦】類在設計和實現時要符合單一原則。

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

⑨【推薦】謹慎使用繼承方式進行擴展,優先考慮聚合/組合方式來實現。

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

⑩【推薦】系統設計時,根據依賴倒置原則,儘量依賴抽象類與接口,有利於擴展與維護。

低層次模塊依賴於高層次模塊的抽象,方便系統間的解耦。

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

極端情況下,交付的代碼都是不可修改的,同一業務域內的需求變化,通過模塊或類的擴展來實現。

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

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

⑬【推薦】避免出現如下誤解:敏捷開發 = 講故事 + 編碼 + 發佈。

敏捷開發是快速交付迭代可用的系統,省略多餘的設計方案,摒棄傳統的審批流程,但核心關鍵點上的必要設計和文檔沉澱是需要的。

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

避免爲了設計而設計,系統設計文檔有助於後期的系統維護,所以設計結果需要進行分類歸檔保存

⑮【參考】設計的本質就是識別和表達系統難點,找到系統的變化點,並隔離變化點。

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

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

 1)確定系統邊界,確定系統在技術層面的做與不做。

 2)確定系統內,模塊之間的關係。確定模塊之間的依賴關係及模塊的宏觀輸入與輸出

 3)確定指導後續設計與演化的原則。使後續的子系統或模塊設計在規定的框架內繼續演化。

 4)確定非功能性需求。非功能性需求是指安全性,可用性,可擴展性。

 

附錄:專有名詞解釋

1. POJO(Plain Ordinary Java Object): 在本手冊中,POJO 專指只有 setter / getter / toString 的簡單類,包括 DO/DTO/BO/VO 等。

2. GAV(GroupId、ArtifactctId、Version): Maven 座標,是用來唯一標識 jar 包。

3. OOP(Object Oriented Programming): 本手冊泛指類、對象的編程處理方式。

4. ORM(Object Relation Mapping): 對象關係映射,對象領域模型與底層數據之間的轉換, 本文泛指 iBATIS, mybatis 等框架。

5. NPE(java.lang.NullPointerException): 空指針異常。

6. SOA(Service-Oriented Architecture): 面向服務架構,它可以根據需求通過網絡對鬆散 耦合的粗粒度應用組件進行分佈式部署、組合和使用,有利於提升組件可重用性,可維護性。
 7. IDE(Integrated Development Environment): 用於提供程序開發環境的應用程序,一般 包括代碼編輯器、編譯器、調試器和圖形用戶界面等工具,本《手冊》泛指 IntelliJ IDEA 和 eclipse。

8. OOM(Out Of Memory): 源於 java.lang.OutOfMemoryError,當 JVM 沒有足夠的內存 來爲對象分配空間並且垃圾回收器也無法回收空間時,系統出現的嚴重狀況。

9. 一方庫: 本工程內部子項目模塊依賴的庫(jar 包)。

10. 二方庫: 公司內部發布到中央倉庫,可供公司內部其它應用依賴的庫(jar 包)。 11. 三方庫: 公司之外的開源庫(jar 包)。 


使用IDEA開發的同學可以下載阿里提供的代碼規範檢測開源插件:https://github.com/alibaba/p3c

 

參考資料:阿里巴巴java技術開發手冊。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章