轉自官網java
前言 linux
《阿里巴巴Java開發手冊》是阿里巴巴集團技術團隊的集體智慧結晶和經驗總結,經歷了屢次大規模一線實戰的檢驗及不斷完善,系統化地整理成冊,回饋給廣大開發者。現代軟件行業的高速發展對開發者的綜合素質要求愈來愈高,由於不只是編程知識點,其它維度的知識點也會影響到軟件的最終交付質量。好比:數據庫的表結構和索引設計缺陷可能帶來軟件上的架構缺陷或性能風險;工程結構混亂致使後續維護艱難;沒有鑑權的漏洞代碼易被黑客攻擊等等。因此本手冊以Java開發者爲中心視角,劃分爲編程規約、異常日誌、單元測試、安全規約、MySQL數據庫、工程結構、設計規約七個維度,再根據內容特徵,細分紅若干二級子目錄。根據約束力強弱及故障敏感性,規約依次分爲強制、推薦、參考三大類。對於規約條目的延伸信息中,「說明」對規約作了適當擴展和解釋;「正例」提倡什麼樣的編碼和實現方式;「反例」 說明須要提防的雷區,以及真實的錯誤案例。 git
本手冊的旨在碼出高效,碼出質量。現代軟件架構的複雜性須要協同開發完成,如何高效地協同呢?無規矩不成方圓,無規範難以協同,好比,制訂交通法規表面上是要限制行車權,其實是保障公衆的人身安全,試想若是沒有限速,沒有紅綠燈,誰還敢上路行駛。對軟件來講,適當的規範和標準毫不是消滅代碼內容的創造性、優雅性,而是限制過分個性化,以一種廣泛承認的統一方式一塊兒作事,提高協做效率,下降溝通成本。代碼的字裏行間流淌的是軟件系統的血液,質量的提高是儘量少踩坑,杜絕踩重複的坑,切實提高系統穩定性,碼出質量。 程序員
考慮到能夠零距離地與衆多開發同窗進行互動,決定將來在線維護《手冊》內容,此1.4.0的PDF版本,是最爲詳盡的版本,新增設計規約大章節,並增長若干條目;咱們已經在2017杭州雲棲大會上發佈了阿里巴巴Java開發規約插件(點此下載),阿里雲效(一站式企業協同研發雲)也集成了代碼規約掃描引擎。最後,《碼出高效—— 阿里巴巴Java開發手冊詳解》即將出版,敬請關注。github
Java開發手冊正則表達式
版本號 spring |
制定團隊 sql |
更新日期 數據庫 |
備註 express |
1.4.0 |
阿里巴巴集團技術團隊 |
2018.5.20 |
增長設計規約(詳盡版) |
反例:_name / __name / $name / name_ / name$ / name__
反例:DaZhePromotion [打折] / getPingfenByName() [評分] / int 某變量 = 3
PO / UID等。
正例:MarcoPolo / UserDO / XmlService / TcpUdpDeal / TaPromotion 反例:macroPolo / UserDo / XMLService / TCPUDPDeal / TAPromotion
正例: localValue / getHttpMessage() / inputUserId
正例:MAX_STOCK_COUNT 反例:MAX_COUNT
正例:應用工具類包名爲com.alibaba.ai.util、類名爲MessageUtils(此規則參考spring 的框架結構)
反例:AbstractClass「縮寫」命名成AbsClass;condition「縮寫」命名成 condi,此類隨意縮寫嚴重下降了代碼的可閱讀性。
正例:在 JDK 中,表達原子更新的類名爲:AtomicReferenceFieldUpdater。
反例:變量 int a 的隨意命名方式。
說明:將設計模式體如今名字中,有利於閱讀者快速理解架構設計理念。
正例:public class OrderFactory; public class LoginProxy;
public class ResourceObserver;
正例:接口方法簽名void commit();
接口基礎常量String COMPANY = "alibaba";
反例:接口方法定義public abstract void f();
說明:JDK8中接口容許有默認實現,那麼這個default方法,是對全部實現類都有價值的默認實現。
1) 【強制】對於Service和DAO類,基於SOA的理念,暴露出來的服務必定是接口,內部的實現類用Impl的後綴與接口區別。
正例:CacheServiceImpl實現CacheService接口。
2) 【推薦】 若是是形容能力的接口名稱,取對應的形容詞爲接口名(一般是–able的形式)。
正例:AbstractTranslator實現 Translatable接口。
說明:枚舉其實就是特殊的類,域成員均爲常量,且構造方法被默認強制是私有。
正例:枚舉名字爲ProcessStatusEnum的成員名稱:SUCCESS / UNKNOWN_REASON。
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。
反例:String key = "Id#taobao_" + tradeId; cache.put(key, value);
1混淆,形成誤解。
說明:Long a = 2l; 寫的是數字的21,仍是Long型的2?
說明:大而全的常量類,雜亂無章,使用查找功能才能定位到修改的常量,不利於理解和維護。
正例:緩存相關常量放在類CacheConsts下;系統配置相關常量放在類ConfigConsts下。
1) 跨應用共享常量:放置在二方庫中,一般是client.jar中的constant目錄下。
2) 應用內共享常量:放置在一方庫中,一般是子模塊中的constant目錄下。
反例:易懂變量也要統必定義成應用內共享常量,兩位攻城師在兩個類中分別定義了表示
「是」的變量:
類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定義。
說明:若是存在名稱以外的延伸屬性應使用 enum 類型,下面正例中的數字就是延伸信息,表示一年中的第幾個季節。
public enum SeasonEnum {
SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4); private int seq;
SeasonEnum(int seq){ this.seq = seq;
}
}
1) 左大括號前不換行。
2) 左大括號後換行。
3) 右大括號前換行。
4) 右大括號後還有else等代碼則不換行;表示終止的右大括號後必須換行。
空格 |
a == b |
空格 |
反例:if ()
說明:運算符包括賦值運算符=、邏輯運算符&&、加減乘除符號等。
說明:若是使用tab縮進,必須設置1個tab爲4個空格。IDEA設置tab爲4個空格時,請勿勾選Use tab character;而在eclipse中,必須勾選insert spaces for tabs。正例: (涉及1-5點)
public static void main(String[] args) {
// 縮進4個空格
String say = "hello";
// 運算符的左右必須有一個空格
int flag = 0;
// 關鍵詞if與括號之間必須有一個空格,括號內的f與左括號,0與右括號不須要空格 if (flag == 0) {
System.out.println(say);
}
// 左大括號前加空格且不換行;左大括號後換行
if (flag == 1) {
System.out.println("world");
// 右大括號前換行,右大括號後有else,不用換行
} else {
System.out.println("ok");
// 在右大括號後直接結束,則必須換行
}
}
6. 【強制】註釋的雙斜線與註釋內容之間有且僅有一個空格。
// 這是示例註釋,請注意在雙斜線以後有一個空格
String ygb = new String();
7. 【強制】單行字符數限制不超過 120 個,超出須要換行,換行時遵循以下原則: 1) 第二行相對第一行縮進 4 個空格,從第三行開始,再也不繼續縮進,參考示例。
2) 運算符與下文一塊兒換行。
3) 方法調用的點符號與下文一塊兒換行。
4) 方法調用中的多個參數須要換行時,在逗號後進行。
5) 在括號前不要換行,見反例。正例:
StringBuffer sb = new StringBuffer();
// 超過120個字符的狀況下,換行縮進4個空格,點號和方法名稱一塊兒換行 sb.append("zi").append("xin")...
.append("huang")...
.append("huang")...
.append("huang");
StringBuffer sb = new StringBuffer();
// 超過120個字符的狀況下,不要在括號前換行
sb.append("zi").append("xin")...append
("huang");
// 參數不少的方法調用可能超過120個字符,不要在逗號前換行 method(args1, args2, args3, ...
, argsX);
正例:下例中實參的args1,後邊必需要有一個空格。
method(args1, args2, args3);
說明:包括方法簽名、結束右大括號、方法內代碼、註釋、空行、回車及任何不可見字符的總行數不超過 80 行。
正例:代碼邏輯分清紅花和綠葉,個性和共性,綠葉邏輯單獨出來成爲額外方法,使主幹代碼更加清晰;共性邏輯抽取成爲共性方法,便於複用和維護。
int one = 1;
long two = 2L;
float three = 3F;
StringBuffer sb = new StringBuffer();
說明:增長sb這個變量,若是須要對齊,則給a、b、c都要增長几個空格,在變量比較多的狀況下,是很是累贅的事情。
12. 【推薦】不一樣邏輯、不一樣語義、不一樣業務的代碼之間插入一個空行分隔開來以提高可讀性。
說明:任何情形,沒有必要插入多個空行進行隔開。
說明:getObject()與get0bject()的問題。一個是字母的O,一個是數字的0,加@Override 能夠準確判斷是否覆蓋成功。另外,若是在抽象類中對方法簽名進行修改,其實現類會立刻編譯報錯。
說明:可變參數必須放置在參數列表的最後。(提倡同窗們儘可能不用可變參數編程)正例:public List<User> listUsers(String type, Long... ids) {...}
說明:java.net.URLDecoder 中的方法decode(String encodeStr) 這個方法已通過時,應
該使用雙參數decode(String source, String encode)。接口提供方既然明確是過期接口,那麼有義務同時提供新的接口;做爲調用方來講,有義務去考證過期方法的新實現是什麼。
正例:"test".equals(object); 反例:object.equals("test");
說明:推薦使用java.util.Objects#equals(JDK7引入的工具類)
說明:對於Integer var = ? 在-128至127範圍內的賦值,Integer對象是在
IntegerCache.cache產生,會複用已有對象,這個區間內的Integer值能夠直接使用==進行判斷,可是這個區間以外的全部數據,都會在堆上產生,並不會複用已有對象,這是一個大坑,推薦使用equals方法進行判斷。
1) 【強制】全部的POJO類屬性必須使用包裝數據類型。
2) 【強制】RPC方法的返回值和參數必須使用包裝數據類型。
3) 【推薦】全部的局部變量使用基本數據類型。
說明:POJO類屬性沒有初值是提醒使用者在須要使用時,必須本身顯式地進行賦值,任何
NPE問題,或者入庫檢查,都由使用者來保證。
正例:數據庫的查詢結果多是null,由於自動拆箱,用基本數據類型接收有NPE風險。 反例:好比顯示成交總額漲跌狀況,即正負x%,x爲基本數據類型,調用的RPC服務,調用不成功時,返回的是默認值,頁面顯示爲0%,這是不合理的,應該顯示成中劃線。因此包裝數據類型的null值,可以表示額外的信息,如:遠程調用失敗,異常退出。
反例:POJO類的gmtCreate默認值爲new Date(),可是這個屬性在數據提取時並無置入具體值,在更新其它字段時又附帶更新了此字段,致使建立時間被修改爲當前時間。
說明:注意serialVersionUID不一致會拋出序列化運行時異常。
說明:在方法執行拋出異常時,能夠直接調用POJO的toString()方法打印其屬性值,便於排查問題。
說明:框架在調用屬性 xxx 的提取方法時,並不能肯定哪一個方法必定是被優先調用到。
String str = "a,b,c,,";
String[] ary = str.split(",");
// 預期大於3,結果是3
System.out.println(ary.length);
說明:公有方法是類的調用者和維護者最關心的方法,首屏展現最好;保護方法雖然只是子類關心,也多是「模板設計模式」下的核心方法;而私有方法外部通常不須要特別關心,是一個黑盒實現;由於承載的信息價值較低,全部Service和DAO的getter/setter方法放在類體最後。
public Integer getData() { if (condition) { return this.data + 100;
} else { return this.data - 100;
}
}
18. 【推薦】循環體內,字符串的鏈接方式,使用StringBuilder的append方法進行擴展。說明:下例中,反編譯出的字節碼文件顯示每次循環都會new出一個StringBuilder對象,而後進行append操做,最後經過toString方法返回String對象,形成內存資源浪費。
String str = "start"; for (int i = 0; i < 100; i++) { str = str + "hello";
}
2) 不容許修改引用的域對象。
3) 不容許被重寫的方法,如:POJO類的setter方法。
4) 不容許運行過程當中從新賦值的局部變量。
5) 避免上下文重複使用一個變量,使用final描述能夠強制從新定義一個變量,方便更好地進行重構。
說明:對象的clone方法默認是淺拷貝,若想實現深拷貝須要重寫clone方法實現域對象的深度遍歷式拷貝。
1) 若是不容許外部直接經過new來建立對象,那麼構造方法必須是private。
2) 工具類不容許有public或default構造方法。
3) 類非static成員變量而且與子類共享,必須是protected。
4) 類非static成員變量而且僅在本類使用,必須是private。
5) 類static成員變量若是僅在本類使用,必須是private。
6) 如果static成員變量,考慮是否爲final。
7) 類成員方法只供類內部調用,必須是private。
8) 類成員方法只對繼承類公開,那麼限制爲protected。
說明:任何類、方法、參數、變量,嚴控訪問範圍。過於寬泛的訪問範圍,不利於模塊解耦。
思考:若是是一個private的方法,想刪除就刪除,但是一個public的service成員方法或成員變量,刪除一下,不得手心冒點汗嗎?變量像本身的小孩,儘可能在本身的視線內,變量做用域太大,無限制的處處跑,那麼你會擔憂的。
1) 只要重寫equals,就必須重寫hashCode。
2) 由於Set存儲的是不重複的對象,依據hashCode和equals進行判斷,因此Set存儲的對象必須重寫這兩個方法。
3) 若是自定義對象做爲Map的鍵,那麼必須重寫hashCode和equals。
說明:String重寫了hashCode和equals方法,因此咱們能夠很是愉快地使用String對象做爲key來使用。
異常,即java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。說明:subList 返回的是 ArrayList 的內部類 SubList,並非 ArrayList而是ArrayList 的一個視圖,對於SubList子列表的全部操做最終會反映到原列表上。
說明:使用toArray帶參方法,入參分配的數組空間不夠大時,toArray方法內部將從新分配內存空間,並返回新數組地址;若是數組元素個數大於實際所需,下標爲[ list.size() ] 的數組元素將被置爲null,其它數組元素保持原值,所以最好將方法入參數組大小定義與集合元素個數一致。
List<String> list = new ArrayList<String>(2); list.add("guan"); list.add("bao");
String[] array = new String[list.size()]; array = list.toArray(array);
反例:直接使用toArray無參方法存在問題,此方法返回值只能是Object[]類,若強轉其它類型數組將出現ClassCastException錯誤。
說明:asList的返回對象是一個Arrays內部類,並無實現集合的修改方法。Arrays.asList 體現的是適配器模式,只是轉換接口,後臺的數據還是數組。
String[] str = new String[] { "you", "wu" }; List list = Arrays.asList(str);
第一種狀況:list.add("yangguanbao"); 運行時異常。
第二種狀況:str[0] = "gujin"; 那麼list.get(0)也會隨之修改。
說明:擴展說一下PECS(Producer Extends Consumer Super)原則:第1、頻繁往外讀取內容的,適合用<? extends T>。第2、常常往裏插入的,適合用<? super T>。
List<String> list = new ArrayList<>(); list.add("1"); list.add("2");
Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) {
String item = iterator.next(); if (刪除元素的條件) { iterator.remove(); }
}
for (String item : list) { if ("1".equals(item)) { list.remove(item);
}
}
說明:以上代碼的執行結果確定會出乎你們的意料,那麼試一下把「1」換成「2」,會是一樣的結果嗎?
Collections.sort會報IllegalArgumentException異常。
說明:三個條件以下
1) x,y的比較結果和y,x的比較結果相反。
2) x>y,y>z,則x>z。
3) x=y,則x,z比較結果和y,z比較結果相同。
反例:下例中沒有處理相等的狀況,實際使用中可能會出現異常:
new Comparator<Student>() {
@Override public int compare(Student o1, Student o2) { return o1.getId() > o2.getId() ? 1 : -1; } };
// <> diamond方式
HashMap<String, String> userCache = new HashMap<>(16);
// 全省略方式
ArrayList<User> users = new ArrayList(10);
說明:HashMap使用HashMap(int initialCapacity) 初始化。
正例:initialCapacity = (須要存儲的元素個數 / 負載因子) + 1。注意負載因子(即loader factor)默認爲0.75,若是暫時沒法肯定初始值大小,請設置爲16(即默認值)。
反例:HashMap須要放置1024個元素,因爲沒有設置容量初始大小,隨着元素不斷增長,容量 7 次被迫擴大,resize須要重建hash表,嚴重影響性能。
說明:keySet實際上是遍歷了2次,一次是轉爲Iterator對象,另外一次是從hashMap中取出 key所對應的value。而entrySet只是遍歷了一次就把key和value都放到了entry中,效率更高。若是是JDK8,使用Map.foreach方法。
正例:values()返回的是V值集合,是一個list集合對象;keySet()返回的是K值集合,是一個Set集合對象;entrySet()返回的是K-V值組合集合。
集合類 |
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異常。
說明:有序性是指遍歷的結果是按某種比較規則依次排列的。穩定性指集合每次遍歷的元素次
序是必定的。如:ArrayList是order/unsort;HashMap是unorder/unsort;TreeSet是 order/sort。
contains方法進行遍歷、對比、去重操做。
public class TimerTaskThread extends Thread { public TimerTaskThread() { super.setName("TimerTaskThread");
...
}
}
3. 【強制】線程資源必須經過線程池提供,不容許在應用中自行顯式建立線程。
說明:使用線程池的好處是減小在建立和銷燬線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題。若是不使用線程池,有可能形成系統建立大量同類線程而致使消耗完內存或者「過分切換」的問題。
4.【強制】線程池不容許使用Executors去建立,而是經過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同窗更加明確線程池的運行規則,規避資源耗盡的風險。
說明:Executors返回的線程池對象的弊端以下:
1)FixedThreadPool和SingleThreadPool:
容許的請求隊列長度爲Integer.MAX_VALUE,可能會堆積大量的請求,從而致使OOM。
2)CachedThreadPool和ScheduledThreadPool:
容許的建立線程數量爲Integer.MAX_VALUE,可能會建立大量的線程,從而致使OOM。
5. 【強制】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代替SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable thread-safe。
說明:儘量使加鎖的代碼塊工做量儘量的小,避免在鎖代碼塊中調用 RPC 方法。
說明:線程一須要對錶A、B、C依次所有加鎖後才能夠進行更新操做,那麼線程二的加鎖順序也必須是A、B、C,不然可能出現死鎖。
鎖,要麼在數據庫層使用樂觀鎖,使用version做爲更新依據。
說明:若是每次訪問衝突機率小於20%,推薦使用樂觀鎖,不然使用悲觀鎖。樂觀鎖的重試次數不得小於3次。
拋出的異常,其它任務便會自動終止運行,使用ScheduledExecutorService則沒有這個問題。
10. 【推薦】使用CountDownLatch進行異步轉同步操做,每一個線程退出前必須調用countDown 方法,線程執行代碼注意catch異常,確保countDown方法被執行到,避免主線程沒法執行至await方法,直到超時才返回結果。
說明:注意,子線程拋出異常堆棧,不能在主線程try-catch到。
11. 【推薦】避免Random實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一 seed 致使的性能降低。
說明:Random實例包括java.util.Random 的實例或者 Math.random()的方式。
正例:在JDK7以後,能夠直接使用API ThreadLocalRandom,而在 JDK7以前,須要編碼保證每一個線程持有一個實例。
12. 【推薦】在併發場景下,經過雙重檢查鎖(double-checked locking)實現延遲初始化的優化問題隱患(可參考 The "Double-Checked Locking is Broken" Declaration),推薦解決方案中較爲簡單一種(適用於JDK5及以上版本),將目標屬性聲明爲 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... }
AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 若是是JDK8,推薦使用LongAdder對象,比AtomicLong性能更好(減小樂觀鎖的重試次數)。
修飾。這個變量是針對一個線程內全部操做共享的,因此設置爲靜態變量,全部此類實例共享此靜態變量 ,也就是說在類第一次被使用時裝載,只分配一塊存儲空間,全部此類的對象(只要是這個線程內定義的)均可以操控這個變量。
單行的編碼方式:if (condition) statements;
3.【強制】在高併發場景中,避免使用」等於」判斷做爲中斷或退出的條件。
說明:若是併發控制沒有處理好,容易產生等值判斷被「擊穿」的狀況,使用大於或小於的區間判斷條件來代替。
反例:判斷剩餘獎品數量等於 0 時,終止發放獎品,但由於併發處理錯誤致使獎品數量瞬間變成了負數,這樣的話,活動沒法終止。
4. 【推薦】表達異常的分支時,少用if-else方式,這種方式能夠改寫成:
if (condition) { ...
return obj;
}
// 接着寫else的業務邏輯代碼; 說明:若是非得使用if()...else if()...else...方式表達邏輯,【強制】避免後續代碼維護困難,請勿超過3層。 正例:超過3層的 if-else 的邏輯判斷代碼能夠使用衛語句、策略模式、狀態模式等來實現,其中衛語句示例以下:
public void today() { if (isBusy()) {
System.out.println(「change time.」);
return;
}
if (isFree()) {
System.out.println(「go to travel.」); return;
}
System.out.println(「stay at home to learn Alibaba Java Coding Guidelines.」); return;
}
5. 【推薦】除經常使用方法(如getXxx/isXxx)等外,不要在條件判斷中執行其它複雜的語句,將複雜邏輯判斷的結果賦值給一個有意義的布爾變量名,以提升可讀性。說明:不少 if 語句內的邏輯至關複雜,閱讀者須要分析條件表達式的最終結果,才能明確什麼樣的條件執行什麼樣的語句,那麼,若是閱讀者分析邏輯表達式錯誤呢?正例:
// 僞代碼以下
final boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed) { ...
}
if ((file.open(fileName, "w") != null) && (...) || (...)) { ...
}
6.【推薦】循環體中的語句要考量性能,如下操做盡可能移至循環體外處理,如定義對象、變量、
獲取數據庫鏈接,進行沒必要要的try-catch操做(這個try-catch是否能夠移至循環體外)。
說明:取反邏輯不利於快速理解,而且取反邏輯寫法必然存在對應的正向邏輯寫法。
正例:使用if (x < 628) 來表達 x 小於 628。
反例:使用if (!(x >= 628)) 來表達 x 小於 628。
1) 調用頻次低的方法。
2) 執行時間開銷很大的方法。此情形中,參數校驗時間幾乎能夠忽略不計,但若是由於參數錯誤致使中間執行回退,或者錯誤,那得不償失。
3) 須要極高穩定性和可用性的方法。
4) 對外提供的開放接口,無論是RPC/API/HTTP接口。
5) 敏感權限入口。
1) 極有可能被循環調用的方法。但在方法說明裏必須註明外部參數檢查要求。
2) 底層調用頻度比較高的方法。畢竟是像純淨水過濾的最後一道,參數錯誤不太可能到底層纔會暴露問題。通常DAO層與Service層都在同一個應用中,部署在同一臺服務器中,因此DAO的參數校驗,能夠省略。
3) 被聲明成private只會被本身代碼所調用的方法,若是可以肯定調用方法的代碼傳入參數已經作過檢查或者確定不會有問題,此時能夠不校驗參數。
// xxx方式。
說明:在IDE編輯窗口中,Javadoc方式會提示相關注釋,生成Javadoc能夠正確輸出相應註釋;在IDE中,工程調用方法時,不進入方法便可懸浮提示方法、參數、返回值的意義,提升閱讀效率。
4.【強制】方法內部單行註釋,在被註釋語句上方另起一行,使用//註釋。方法內部多行註釋使用/* */註釋,注意與代碼對齊。
反例:「TCP鏈接超時」解釋成「傳輸控制協議鏈接超時」,理解反而費腦筋。
說明:代碼與註釋更新不一樣步,就像路網與導航軟件更新不一樣步同樣,若是導航軟件嚴重滯後,就失去了導航的意義。
說明:代碼被註釋掉有兩種可能性:1)後續會恢復此段代碼邏輯。2)永久不用。前者若是沒有備註信息,難以知曉註釋動機。後者建議直接刪掉(代碼倉庫保存了歷史代碼)。
10. 【參考】好的命名、代碼結構是自解釋的,註釋力求精簡準確、表達到位。避免出現註釋的一個極端:過多過濫的註釋,代碼的邏輯一旦修改,修改註釋是至關大的負擔。
// put elephant into fridge put(elephant, fridge);
方法名put,加上兩個有意義的變量名elephant和fridge,已經說明了這是在幹什麼,語義清晰的代碼不須要額外的註釋。
11. 【參考】特殊註釋標記,請註明標記人與標記時間。注意及時處理這些標記,經過標記掃描,常常清理此類標記。線上故障有時候就是來源於這些標記處的代碼。
1) 待辦事宜(TODO):( 標記人,標記時間,[預計處理時間])
表示須要實現,但目前還未實現的功能。這其實是一個Javadoc的標籤,目前的Javadoc 尚未實現,但已經被普遍使用。只能應用於類,接口和方法(由於它是一個Javadoc標籤)。
2) 錯誤,不能工做(FIXME):(標記人,標記時間,[預計處理時間])
在註釋中用FIXME標記某代碼是錯誤的,並且不能工做,須要及時糾正的狀況。
說明:不要在方法體內定義:Pattern pattern = Pattern.compile(「規則」);
規範調用POJO的getXxx(),若是是boolean基本數據類型變量(boolean命名不須要加is 前綴),會自動調用isXxx()方法。
說明:注意若是是Boolean包裝類對象,優先調用getXxx()的方法。
說明:若是var等於null或者不存在,那麼${var}會直接顯示在頁面上。
說明:根據 MVC 理論,視圖的職責是展現,不要搶模型和控制器的活。
說明:對於垃圾代碼或過期配置,堅定清理乾淨,避免程序過分臃腫,代碼冗餘。
正例:對於暫時被註釋掉,後續可能恢復使用的代碼片段,在註釋代碼上方,統一規定使用三個斜槓(///)來講明註釋掉代碼的理由。
說明:沒法經過預檢查的異常除外,好比,在解析字符串形式的數字時,不得不經過catch
NumberFormatException來實現。
正例:if (obj != null) {...}
反例:try { obj.method(); } catch (NullPointerException e) {…}
說明:異常設計的初衷是解決程序運行中的各類意外狀況,且異常的處理效率比條件判斷方式要低不少。
對於非穩定代碼的catch儘量進行區分異常類型,再作對應的異常處理。
說明:對大段代碼進行try-catch,使程序沒法根據不一樣的異常作出正確的應激反應,也不利於定位問題,這是一種不負責任的表現。
正例:用戶註冊的場景中,若是用戶輸入非法字符,或用戶名稱已存在,或用戶輸入密碼過於簡單,在程序上做出分門別類的判斷,並提示給用戶。
說明:若是JDK7及以上,能夠使用try-with-resources方式。
說明:finally塊中的return返回後方法結束執行,不會再執行try塊中的return語句。
說明:若是預期對方拋的是繡球,實際接到的是鉛球,就會產生意外狀況。
說明什麼狀況下會返回null值。
說明:本手冊明確防止NPE是調用者的責任。即便被調用方法返回空集合或者空對象,對調用者來講,也並不是高枕無憂,必須考慮到遠程調用失敗、序列化失敗、運行時異常等場景返回 null的狀況。
1) 返回類型爲基本數據類型,return包裝數據類型的對象時,自動拆箱有可能產生NPE。 反例:public int f() { return Integer對象}, 若是爲null,自動解箱拋NPE。
2) 數據庫的查詢結果可能爲null。
3) 集合裏的元素即便isNotEmpty,取出的數據元素也可能爲null。
4) 遠程調用返回對象時,一概要求進行空指針判斷,防止NPE。
5) 對於Session中獲取的數據,建議NPE檢查,避免空指針。
6) 級聯調用obj.getA().getB().getC();一連串調用,易產生NPE。
正例:使用JDK8的Optional類來防止NPE問題。
說明:關於RPC方法返回方式使用Result方式的理由:
1) 使用拋異常返回方式,調用方若是沒有捕獲到就會產生運行時錯誤。
2) 若是不加棧信息,只是new自定義異常,加入本身的理解的error message,對於調用端解決問題的幫助不會太多。若是加了棧信息,在頻繁調用出錯的狀況下,數據序列化和傳輸的性能損耗也是問題。
說明:隨意複製和粘貼代碼,必然會致使代碼的重複,在之後須要修改時,須要修改全部的副本,容易遺漏。必要時抽取共性方法,或者抽象公共類,甚至是組件化。
正例:一個類中有多個public方法,都須要進行數行相同的參數校驗操做,這個時候請抽取:
private boolean checkParam(DTO dto) {...}
SLF4J中的API,使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式統一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
appName_logType_logName.log。
logType:日誌類型,如stats/monitor/access等;logName:日誌描述。這種命名的好處:經過文件名就可知道日誌文件屬於什麼應用,什麼類型,什麼目的,也有利於歸類查找。
正例:mppserver應用中單獨監控時區轉換異常,如:
mppserver_monitor_timeZoneConvert.log
說明:推薦對日誌進行分類,如將錯誤日誌和業務日誌分開存放,便於開發人員查看,也便於經過日誌對系統進行及時監控。
說明:logger.debug("Processing trade with id: " + id + " and symbol: " + symbol); 若是日誌級別是warn,上述日誌不會打印,可是會執行字符串拼接操做,若是symbol是對象,會執行toString()方法,浪費了系統資源,執行了上述操做,最終日誌卻沒有打印。
正例:(條件)建設採用以下方式
if (logger.isDebugEnabled()) {
logger.debug("Processing trade with id: " + id + " and symbol: " + symbol);
}
logger.debug("Processing trade with id: {} and symbol : {} ", id, symbol);
正例:<logger name="com.taobao.dubbo.config" additivity="false">
正例:logger.error(各種參數或者對象toString() + "_" + e.getMessage(), e);
說明:大量地輸出無效日誌,不利於系統性能提高,也不利於快速定位錯誤點。記錄日誌時請思考:這些日誌真的有人看嗎?看到這條日誌你能作什麼?能不能給問題排查帶來好處?
從。如非必要,請不要在此場景打出 error 級別,避免頻繁報警。
說明:注意日誌輸出的級別,error 級別只記錄系統邏輯出錯、異常或者重要的錯誤信息。
說明:單元測試在線上運行時,感受像空氣(AIR)同樣並不存在,但在測試質量的保障上,倒是很是關鍵的。好的單元測試宏觀上來講,具備自動化、獨立性、可重複執行的特色。
l A:Automatic(自動化)
l I:Independent(獨立性)
l R:Repeatable(可重複)
反例:method2須要依賴method1的執行,將執行結果做爲method2的輸入。
說明:單元測試一般會被放到持續集成中,每次有代碼check in時單元測試都會被執行。若是單測對外部環境(網絡、服務、中間件等)有依賴,容易致使持續集成機制的不可用。
正例:爲了避免受外界環境影響,要求設計代碼時就把SUT的依賴改爲注入,在測試時用spring 這樣的DI框架注入一個本地(內存)實現或者Mock實現。
說明:只有測試粒度小才能在出錯時儘快定位到出錯位置。單測不負責檢查跨類或者跨系統的交互邏輯,那是集成測試的領域。
說明:新增代碼及時補充單元測試,若是新增代碼影響了原有單元測試,請及時修正。
說明:源碼構建時會跳過此目錄,而單元測試框架默認是掃描此目錄。
說明:在工程規約的應用分層中提到的DAO層,Manager層,可重用度高的Service,都應該進行單元測試。
l B:Border,邊界值測試,包括循環邊界、特殊取值、特殊時間點、數據順序等。
l C:Correct,正確的輸入,並獲得預期的結果。
l D:Design,與設計文檔相結合,來編寫單元測試。
l E:Error,強制錯誤信息輸入(如:非法數據、異常流程、非業務容許輸入等),並獲得預期的結果。
反例:刪除某一行數據的單元測試,在數據庫中,先直接手動增長一行做爲刪除目標,可是這一行新增數據並不符合業務插入規則,致使測試結果異常。
正例:在RDC內部單元測試中,使用RDC_UNIT_TEST_的前綴標識數據。
l 構造方法中作的事情過多。
l 存在過多的全局變量和靜態方法。
l 存在過多的外部依賴。
l 存在過多的條件語句。
說明:多層條件語句建議使用衛語句、策略模式、狀態模式等方式重構。
l 那是測試同窗乾的事情。本文是開發手冊,凡是本文內容都是與開發同窗強相關的。 l 單元測試代碼是多餘的。系統的總體功能與各單元部件的測試正常與否是強相關的。
l 單元測試代碼不須要維護。一年半載後,那麼單元測試幾乎處於廢棄狀態。
l 單元測試與線上故障沒有辯證關係。好的單元測試可以最大限度地規避線上故障。
說明:防止沒有作水平權限校驗就可隨意訪問、修改、刪除別人的數據,好比查看他人的私信內容、修改他人的訂單。
說明:中國大陸我的手機號碼顯示爲:158****9119,隱藏中間4位,防止隱私泄露。
說明:忽略參數校驗可能致使:
l page size過大致使內存溢出
l 惡意order by致使數據庫慢查詢
l 任意重定向
l SQL注入
l 反序列化注入
l 正則輸入源串拒絕服務ReDoS
說明:Java 代碼用正則來驗證客戶端的輸入,有些正則寫法驗證普通用戶輸入沒有問題,可是若是攻擊人員使用的是特殊構造的字符串來驗證,有可能致使死循環的結果。
說明:CSRF(Cross-site request forgery)跨站請求僞造是一類常見編程漏洞。對於存在
CSRF漏洞的應用/網站,攻擊者能夠事先構造好URL,只要受害者用戶一訪問,後臺便在用戶不知情的狀況下對數據庫中用戶參數進行相應修改。
說明:如註冊時發送驗證碼到手機,若是沒有限制次數和頻率,那麼能夠利用此功能騷擾到其它用戶,並形成短信平臺資源浪費。
(1 表示是,0 表示否)。
說明:任何字段若是爲非負數,必須是unsigned。
注意:POJO類中的任何布爾類型的變量,都不要加is前綴,因此,須要在<resultMap>設置從is_xxx到Xxx的映射關係。數據庫表示是與否的值,使用tinyint類型,堅持is_xxx的命名方式是爲了明確其取值含義與取值範圍。
正例:表達邏輯刪除的字段名is_deleted,1 表示刪除,0 表示未刪除。
說明:MySQL在Windows下不區分大小寫,但在 Linux 下默認是區分大小寫。所以,數據庫名、表名、字段名,都不容許出現任何大寫字母,避免節外生枝。
正例:aliyun_admin,rdc_config,level3_name 反例:AliyunAdmin,rdcConfig,level_3_name
說明:表名應該僅僅表示表裏面的實體內容,不該該表示實體數量,對應於DO類名也是單數形式,符合表達習慣。
說明:pk_ 即primary key;uk_ 即 unique key;idx_ 即index的簡稱。
說明:float和double在存儲的時候,存在精度損失的問題,極可能在值的比較時,獲得不
正確的結果。若是存儲的數據範圍超過decimal的範圍,建議將數據拆成整數和小數分開存儲。
說明:其中id必爲主鍵,類型爲bigint unsigned、單表時自增、步長爲 1。gmt_create,
gmt_modified的類型均爲datetime類型,前者如今時表示主動建立,後者過去分詞表示被動更新。
正例:alipay_task / force_project / trade_config
(1 不是頻繁修改的字段。
(2 不是varchar超長字段,更不能是text字段。
正例:商品類目名稱使用頻率高,字段長度短,名稱基本一成不變,可在相關聯的表中冗餘存儲類目名稱,避免關聯查詢。
說明:若是預計三年後的數據量根本達不到這個級別,請不要在建立表時就分庫分表。
正例:以下表,其中無符號值能夠避免誤存負數,且擴大了表示範圍。
對象 |
年齡區間 |
類型 |
字節 |
表示範圍 |
人 |
150歲以內 |
tinyint unsigned |
1 |
無符號值:0到255 |
龜 |
數百歲 |
smallint unsigned |
2 |
無符號值:0到65535 |
恐龍化石 |
數千萬年 |
int unsigned |
4 |
無符號值:0到約42.9億 |
太陽 |
約50億年 |
bigint unsigned |
8 |
無符號值:0到約10的19次方 |
說明:不要覺得惟一索引影響了insert速度,這個速度損耗能夠忽略,但提升查找速度是明顯的;另外,即便在應用層作了很是完善的校驗控制,只要沒有惟一索引,根據墨菲定律,必然有髒數據產生。
說明:即便雙表join也要注意表索引、SQL性能。
說明:索引文件具備B-Tree的最左前綴匹配特性,若是左邊的值未肯定,那麼沒法使用此索引。
正例:where a=? and b=? order by c; 索引:a_b_c
反例:索引中有範圍查找,那麼索引有序性沒法利用,如:WHERE a>10 ORDER BY b; 索引 a_b沒法排序。
說明:若是一本書須要知道第11章是什麼標題,會翻開第11章對應的那一頁嗎?目錄瀏覽一下就好,這個目錄就是起到覆蓋索引的做用。
正例:可以創建索引的種類分爲主鍵索引、惟一索引、普通索引三種,而覆蓋索引只是一種查詢的一種效果,用explain的結果,extra列會出現:using index。
說明:MySQL並非跳過offset行,而是取offset+N行,而後返回放棄前offset行,返回 N行,那當offset特別大的時候,效率就很是的低下,要麼控制返回的總頁數,要麼對超過特定閾值的頁數進行SQL改寫。
正例:先快速定位須要獲取的id段,而後再關聯:
SELECT a.* FROM 表1 a, (select id from 表1 where 條件 LIMIT 100000,20 ) b where a.id=b.id
1) consts 單表中最多隻有一個匹配行(主鍵或者惟一索引),在優化階段便可讀取到數據。
2) ref 指的是使用普通的索引(normal index)。
3) range 對索引進行範圍檢索。
反例:explain表的結果,type=index,索引物理文件全掃描,速度很是慢,這個index級別比較range還低,與全表掃描是小巫見大巫。
正例:若是where a=? and b=? ,若是a列的幾乎接近於惟一值,那麼只須要單建idx_a 索引便可。
說明:存在非等號和等號混合時,在建索引時,請把等號條件的列前置。如:where c>? and d=? 那麼即便c的區分度更高,也必須把d放在索引的最前列,即索引idx_d_c。
1) 寧濫勿缺。認爲一個查詢就須要建一個索引。
2) 寧缺勿濫。認爲索引會消耗空間、嚴重拖慢更新和新增速度。
3) 抵制唯一索引。認爲業務的唯一性一概須要在應用層經過「先查後插」方式解決。
說明:count(*)會統計值爲NULL的行,而count(列名)不會統計此列爲NULL值的行。
NULL,所以使用sum()時需注意NPE問題。
正例:能夠使用以下方式來避免sum的NPE問題:SELECT IF(ISNULL(SUM(g)),0,SUM(g))
FROM table;
說明:NULL與任何值的直接比較都爲NULL。
1) NULL<>NULL的返回結果是NULL,而不是false。
2) NULL=NULL的返回結果是NULL,而不是true。
3) NULL<>1的返回結果是NULL,而不是true。
說明:以學生和成績的關係爲例,學生表中的student_id是主鍵,那麼成績表中的student_id 則爲外鍵。若是更新學生表中的student_id,同時觸發成績表中的student_id更新,即爲級聯更新。外鍵與級聯更新適用於單機低併發,不適合分佈式、高併發集羣;級聯更新是強阻塞,存在數據庫更新風暴的風險;外鍵影響數據庫的插入速度。
10. 【參考】若是有國際化須要,全部的字符存儲與表示,均以utf-8編碼,注意字符統計函數的區別。
說明:
SELECT LENGTH("輕鬆工做"); 返回爲12
SELECT CHARACTER_LENGTH("輕鬆工做"); 返回爲4
若是須要存儲表情,那麼選擇utf8mb4來進行存儲,注意它與utf-8編碼的區別。
11. 【參考】 TRUNCATE TABLE 比 DELETE 速度快,且使用的系統和事務日誌資源少,但TRUNCATE 無事務且不觸發trigger,有可能形成事故,故不建議在開發代碼中使用此語句。
說明:TRUNCATE TABLE 在功能上與不帶 WHERE 子句的 DELETE 語句相同。
說明:1)增長查詢分析器解析成本。2)增減字段容易與resultMap配置不一致。3)無用字段增長網絡消耗,尤爲是text類型的字段。
說明:參見定義POJO類以及數據庫字段定義規定,在<resultMap>中增長映射,是必須的。在MyBatis Generator生成的代碼中,須要進行對應的修改。
說明:配置映射關係,使字段與DO類解耦,方便維護。
說明:其實現方式是在數據庫取到statementName對應的SQL語句的全部記錄,再經過subList 取start,size的子集合。
正例:Map<String, Object> map = new HashMap<>();
map.put("start", start); map.put("size", size);
說明:resultClass=」Hashtable」,會置入字段名和屬性值,可是值的類型不可控。
10. 【參考】<isEqual>中的compareValue是與屬性值對比的常量,通常是數字,表示相等時帶上此條件;<isNotEmpty>表示不爲空且不爲null時執行;<isNotNull>表示不爲null值時執行。
Web層,也能夠直接依賴於Service層,依此類推:
JSP渲染,移動端展現等。
1) 對第三方平臺封裝的層,預處理返回結果及轉化異常信息;
2) 對Service層通用能力的下沉,如緩存方案、中間件通用處理;
3) 與DAO層交互,對多個DAO的組合複用。
行catch,使用catch(Exception e)方式,並throw new DAOException(e),不須要打印日誌,由於日誌在Manager/Service層必定須要捕獲並打印到日誌文件中去,若是同臺服務器再打日誌,浪費性能和存儲。在Service層出現異常時,必須記錄出錯日誌到磁盤,儘量帶上參數信息,至關於保護案發現場。若是Manager層與Service同機部署,日誌方式與DAO 層處理一致,若是是單獨部署,則採用與Service一致的處理方式。Web層毫不應該繼續往上拋異常,由於已經處於頂層,若是意識到這個異常將致使頁面沒法正常渲染,那麼就應該直接跳轉到友好錯誤頁面,加上用戶容易理解的錯誤提示信息。開放接口層要將異常處理成錯誤碼和錯誤信息方式返回。
1) GroupID格式:com.{公司/BU }.業務線 [.子業務線],最多4級。
說明:{公司/BU} 例如:alibaba/taobao/tmall/aliexpress等BU一級;子業務線可選。
正例:com.taobao.jstorm 或 com.alibaba.dubbo.register
2) ArtifactID格式:產品線名-模塊名。語義不重複不遺漏,先到中央倉庫去查證一下。
正例:dubbo-client / fastjson-api / jstorm-tool
3) Version:詳細規定參考下方。
1) 主版本號:產品方向改變,或者大規模API不兼容,或者架構不兼容升級。
2) 次版本號:保持相對兼容性,增長主要功能特性,影響範圍極小的API不兼容修改。
3) 修訂號:保持徹底兼容性,修復BUG、新增次要功能特性等。
說明:注意起始版本號必須爲:1.0.0,而不是0.0.1 正式發佈的類庫必須先去中央倉庫進行查證,使版本號有延續性,正式版本號不容許覆蓋升級。如當前版本:1.3.3,那麼下一個合理的版本號:1.3.4 或 1.4.0 或 2.0.0
說明:不依賴SNAPSHOT版本是保證應用發佈的冪等性。另外,也能夠加快編譯時的打包構建。
必須明確評估和驗證,建議進行dependency:resolve先後信息比對,若是仲裁結果徹底不一致,那麼經過dependency:tree命令,找出差別點,進行<excludes>排除jar包。
說明:依賴springframework-core,-context,-beans,它們都是同一個版本,能夠定義一個變量來保存版本:${spring.version},定義依賴的時候,引用該版本。
Version。
說明:在本地調試時會使用各子項目指定的版本號,可是合併成一個war,只能有一個版本號出如今最後的lib目錄中。可能出現線下調試是正確的,發佈到線上卻出故障的問題。
<dependencyManagement>語句塊中。
說明:<dependencyManagement>裏只是聲明版本,並不實現引入,所以子項目須要顯式的聲
明依賴,version和scope都讀取自父pom。而<dependencies>全部聲明在主pom的
<dependencies>裏的依賴都會自動引入,並默認被全部的子項目繼承。
1) 精簡可控原則。移除一切沒必要要的API和依賴,只包含 Service API、必要的領域模型對象、Utils類、常量、枚舉等。若是依賴其它二方庫,儘可能是provided引入,讓二方庫使用者去依賴具體版本號;無log具體實現,只依賴日誌框架。
2) 穩定可追溯原則。每一個版本的變化應該被記錄,二方庫由誰維護,源碼在哪裏,都須要能方便查到。除非用戶主動升級版本,不然公共二方庫的行爲不該該發生變化。
說明:操做系統默認240秒後,纔會關閉處於time_wait狀態的鏈接,在高併發訪問下,服務器端會由於處於time_wait的鏈接數太多,可能沒法創建新的鏈接,因此須要在服務器上調小此等待值。
正例:在linux服務器上請經過變動/etc/sysctl.conf文件去修改該缺省值(秒): net.ipv4.tcp_fin_timeout = 30
說明:主流操做系統的設計是將TCP/UDP鏈接採用與文件同樣的方式去管理,即一個鏈接對應於一個fd。主流的linux服務器默認所支持最大fd數量爲1024,當併發鏈接數很大時很容易由於fd不足而出現「open too many files」錯誤,致使新的鏈接沒法創建。 建議將linux 服務器所支持的最大句柄數調高數倍(與服務器的內存數量相關)。
說明:OOM的發生是有機率的,甚至相隔數月纔出現一例,出錯時的堆內信息對解決問題很是有幫助。
說明:有缺陷的底層數據結構容易致使系統風險上升,可擴展性降低,重構成本也會因歷史數據遷移和系統平滑過渡而陡然增長,因此,存儲方案和數據結構須要認真地進行設計和評審,生產環境提交執行後,須要進行double check。
正例:評審內容包括存儲介質選型、表結構設計可否知足技術方案、存取性能和存儲空間可否知足業務發展、表或字段之間的辯證關係、字段名稱、字段類型、索引等;數據結構變動(如在原有表中新增字段)也須要進行評審經過後上線。
說明:狀態圖的核心是對象狀態,首先明確對象有多少種狀態,而後明確兩兩狀態之間是否存在直接轉換關係,再明確觸發狀態轉換的條件是什麼。
正例:淘寶訂單狀態有已下單、待付款、已付款、待發貨、已發貨、已收貨等。好比已下單與已收貨這兩種狀態之間是不可能有直接轉換關係的。
說明:時序圖反映了一系列對象間的交互與協做關係,清晰立體地反映系統的調用縱深鏈路。
說明:類圖像建築領域的施工圖,若是搭平房,可能不須要,但若是建造螞蟻Z空間大樓,確定須要詳細的施工圖。
說明:活動圖是流程圖的擴展,增長了可以體現協做關係的對象泳道,支持表示併發等。
說明:單一原則最易理解倒是最難實現的一條規則,隨着系統演進,不少時候,忘記了類設計的初衷。
說明:不得已使用繼承的話,必須符合里氏代換原則,此原則說父類可以出現的地方子類必定可以出現,好比,「把錢交出來」,錢的子類美圓、歐元、人民幣等均可以出現。
10.【推薦】系統設計時,根據依賴倒置原則,儘可能依賴抽象類與接口,有利於擴展與維護。
說明:低層次模塊依賴於高層次模塊的抽象,方便系統間的解耦。
11.【推薦】系統設計時,注意對擴展開放,對修改閉合。
說明:極端狀況下,交付的代碼都是不可修改的,同一業務域內的需求變化,經過模塊或類的擴展來實現。
12.【推薦】系統設計階段,共性業務或公共行爲抽取出來公共模塊、公共配置、公共類、公共方法等,避免出現重複代碼或重複配置的狀況。
說明:隨着代碼的重複次數不斷增長,維護成本指數級上升。
13. 【推薦】避免以下誤解:敏捷開發 = 講故事 + 編碼 + 發佈。
說明:敏捷開發是快速交付迭代可用的系統,省略多餘的設計方案,摒棄傳統的審批流程,但核心關鍵點上的必要設計和文檔沉澱是須要的。
反例:某團隊爲了業務快速發展,敏捷成了產品經理催進度的藉口,系統中均是勉強能運行但
像麪條同樣的代碼,可維護性和可擴展性極差,一年以後,不得不進行大規模重構,得不償失。
14.【參考】系統設計主要目的是明確需求、理順邏輯、後期維護,次要目的用於指導編碼。
說明:避免爲了設計而設計,系統設計文檔有助於後期的系統維護,因此設計結果須要進行分類歸檔保存。
15.【參考】設計的本質就是識別和表達系統難點,找到系統的變化點,並隔離變化點。
說明:世間衆多設計模式目的是相同的,即隔離系統變化點。
16. 【參考】系統架構設計的目的:
l 肯定系統邊界。肯定系統在技術層面上的作與不作。
l 肯定系統內模塊之間的關係。肯定模塊之間的依賴關係及模塊的宏觀輸入與輸出。
l 肯定指導後續設計與演化的原則。使後續的子系統或模塊設計在規定的框架內繼續演化。
l 肯定非功能性需求。非功能性需求是指安全性、可用性、可擴展性等。
版本號 |
更新日期 |
備註 |
1.0.0 |
2017.2.9 |
阿里巴巴集團正式對外發布 |
1.0.1 |
2017.2.13 |
1)修正String[]的先後矛盾。2)vm修正成velocity。3)修正countdown描述錯誤。 |
1.0.2 |
2017.2.20 |
1)去除文底水印。2)數據類型中引用太陽系年齡問題。3)修正關於異常和方法簽名的部分描述。4)修正final描述。5)去除Comparator部分描述。 |
1.1.0 |
2017.2.27 |
1)增長前言。2)增長<? extends T>描述和說明。3)增長版本歷史。4)增長專有名詞解釋。 |
1.1.1 |
2017.3.31 |
修正頁碼總數和部分示例。 |
1.2.0 |
2017.5.20 |
1)根據雲棲社區的「聚能聊」活動反饋,對手冊的頁碼、排版、描述進行修正。2)增長 final 的適用場景描述。3)增長關於鎖的粒度的說明。4)增長「指定集合大小」的詳細說明以及正反例。5)增長衛語句的示例代碼。6)明確數據庫表示刪除概念的字段名爲 is_deleted |
1.3.0 |
2017.9.25 |
增長單元測試規約(PDF終極版),阿里開源的IDE代碼規約檢測插件:點此下載更多及時信息,請關注《阿里巴巴Java開發手冊》官方公衆號:
|
1.3.1 |
2017.11.30 |
修正部分描述;採用和P3C開源IDE檢測插件相同的Apache2.0協議。 |
1.4.0 |
2018.5.20 |
增長設計規約(詳盡版) |
耦合的粗粒度應用組件進行分佈式部署、組合和使用,有利於提高組件可重用性,可維護性。