title: Java開發規範
date: 2018-10-08 14:01:59
tags: Java
categories: Java
---java
前言mysql
本文章是將網上PDF版本的Java開發規範轉換成的Markdown版本linux
Java 開發手冊git
版本號 | 做者 | 日期 | 備註 |
---|---|---|---|
1.4.0 | 阿里巴巴集團技術團隊 | 2018. 5. 20 | 增長設計規約(詳盡版) |
【強制】代碼中的命名均不能以下劃線或美圓符號開始,也不能以下劃線或美圓符號 結束。程序員
反例:_name __name $name name_ name$ name__
github
【強制】代碼中的命名嚴禁使用拼音與英文混合的方式,更不容許直接使用中文的方式。正則表達式
正例:正確的英文拼寫和語法可讓閱讀者易於理解,避免歧義。注意,即便純拼音命名方式也要避免採用。spring
alibaba
taobao
youku
hangzhou
等國際通用的名稱,可視同英文。DaZhePromotion [打折]
getPingfenByName() [評分]
int 某變量 = 3
MarcoPolo
UserDO
XmlService
TcpUdpDeal
TaPromotion
macroPolo
UserDo
XMLService
TCPUDPDeal
TAPromotion
lowerCamelCase
風格,必須聽從駝峯形式。localValue
getHttpMessage()
inputUserId
MAX_STOCK_COUNT
MAX_COUNT
Abstract
或Base
開頭;異常類命名使用Exception
結尾;測試類命名以它要測試的類的名稱開始,以Test
結尾。int[] arrayDemo
;String args[]
來定義。POJO
類中布爾類型的變量,都不要加is前綴,不然部分框架解析會引發序列化錯誤。Boolean isDeleted
的屬性,它的方法也是isDeleted()
,RPC框架在反向解析的時候,"誤覺得"對應的屬性名稱是deleted
,致使屬性獲取不到,進而拋出異常。com.alibaba.ai.util
、類名爲MessageUtils
(此規則參考spring的框架結構)AbstractClass
"縮寫"命名成AbsClass
;condition
"縮寫"命名成 condi
,此類隨意縮寫嚴重下降了代碼的可閱讀性。AtomicReferenceFieldUpdater
。int a
的隨意命名方式。【推薦】若是模塊、接口、類、方法使用了設計模式,在命名時需體現出具體模式。sql
將設計模式體如今名字中,有利於閱讀者快速理解架構設計理念。數據庫
正例:
public class OrderFactory; public class LoginProxy; public class ResourceObserver;
//接口方法簽名 void commit(); //接口基礎常量 String COMPANY = "alibaba";
反例:
//接口方法定義 public abstract void f();
> 說明:JDK 8 中接口容許有默認實現,那麼這個default方法,是對全部實現類都有價值的默認實現。
Service
和DAO
類,基於SOA的理念,暴露出來的服務必定是接口,內部的實現類用Impl的後綴與接口區別。CacheServiceImpl
實現CacheService
接口。AbstractTranslator
實現 Translatable
接口。【參考】枚舉類名建議帶上Enum
後綴,枚舉成員名稱須要全大寫,單詞間用下劃線隔開。
正例:枚舉名字爲說明:枚舉其實就是特殊的類,域成員均爲常量,且構造方法被默認強制是私有。
ProcessStatusEnum
的成員名稱:SUCCESS
、UNKNOWN_REASON
。Service
/DAO
層方法命名規約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);
long
或者Long
賦值時,數值後使用大寫的L,不能是小寫的l,小寫容易跟數字1混淆,形成誤解。Long a = 2l;
寫的是數字的 21
,仍是Long
型的2?CacheConsts
下;系統配置相關常量放在類ConfigConsts
下。client.jar
中的constant
目錄下。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
類型來定義。enum
類型,下面正例中的數字就是延伸信息,表示一年中的第幾個季節。public enum SeasonEnum { SPRING( 1 ), SUMMER( 2 ), AUTUMN( 3 ), WINTER( 4 ); private int seq; SeasonEnum(int seq){ this.seq = seq; } }
else
等代碼則不換行;表示終止的右大括號後必須換行。if (空格a == b空格)
if
/for
/while
/switch
/do
等保留字與括號之間都必須加空格
。=
、邏輯運算符&&
、加減乘除符號等。【強制】採用 4
個空格縮進,禁止使用tab
字符。
說明:若是使用
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"); // 在右大括號後直接結束,則必須換行 } }
// 這是示例註釋,請注意在雙斜線以後有一個空格 String ygb = new String();
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);
text file encoding
設置爲UTF- 8
; IDE中文件的換行符使用Unix
格式,不要使用Windows
格式。【推薦】單個方法的總行數不超過 80
行。
正例:代碼邏輯分清紅花和綠葉,個性和共性,綠葉邏輯單獨出來成爲額外方法,使主幹代碼更加清晰;共性邏輯抽取成爲共性方法,便於複用和維護。說明:包括方法簽名、結束右大括號、方法內代碼、註釋、空行、回車及任何不可見字符的總行數不超過 80 行。
int one = 1; long two = 2L; float three = 3F; StringBuffer sb = new StringBuffer();
> 說明:增長sb這個變量,若是須要對齊,則給a、b、c都要增長几個空格,在變量比較多的狀況下,是很是累贅的事情。
【推薦】不一樣邏輯、不一樣語義、不一樣業務的代碼之間插入一個空行分隔開來以提高可讀性。
說明:任何情形,沒有必要插入多個空行進行隔開。
【強制】全部的覆寫方法,必須加@Override
註解。
說明:
getObject()
與get 0 bject()
的問題。一個是字母的O
,一個是數字的0
,加@Override
能夠準確判斷是否覆蓋成功。另外,若是在抽象類中對方法簽名進行修改,其實現類會立刻編譯報錯。
【強制】相同參數類型,相同業務含義,才能夠使用Java
的可變參數,避免使用Object
。
說明:可變參數必須放置在參數列表的最後。(提倡同窗們儘可能不用可變參數編程)
正例:
public List<User> listUsers(String type, Long... ids) {...}
@Deprecated
註解,並清晰地說明採用的新接口或者新服務是什麼。【強制】不能使用過期的類或方法。
說明:
java.net.URLDecoder
中的方法decode(String encodeStr)
這個方法已通過時,應該使用雙參數decode(String source, String encode)
。接口提供方既然明確是過期接口,那麼有義務同時提供新的接口;做爲調用方來講,有義務去考證過期方法的新實現是什麼。
Object
的equals
方法容易拋空指針異常,應使用常量或肯定有值的對象來調用equals
。"test".equals(object);
object.equals("test");
java.util.Objects#equals(JDK 7 引入的工具類)
【強制】全部的相同類型的包裝類對象之間值的比較,所有使用equals
方法比較。
說明:對於
Integer var =?
在-128 至 127
範圍內的賦值,Integer
對象是在IntegerCache.cache
產生,會複用已有對象,這個區間內的Integer
值能夠直接使用==
進行判斷,可是這個區間以外的全部數據,都會在堆上產生,並不會複用已有對象,這是一個大坑,推薦使用equals
方法進行判斷。
關於基本數據類型與包裝數據類型的使用標準以下:
1 )【強制】全部的POJO
類屬性必須使用包裝數據類型。
2 )【強制】RPC
方法的返回值和參數必須使用包裝數據類型。
3 )【推薦】全部的局部變量使用基本數據類型。
說明:
POJO
類屬性沒有初值是提醒使用者在須要使用時,必須本身顯式地進行賦值,任何NPE
問題,或者入庫檢查,都由使用者來保證。
正例:數據庫的查詢結果多是null
,由於自動拆箱,用基本數據類型接收有NPE
風險。
反例:好比顯示成交總額漲跌狀況,即正負x%
,x
爲基本數據類型,調用的RPC
服務,調用不成功
時,返回的是默認值
,頁面顯示爲0%
,這是不合理的,應該顯示成中劃線。因此包裝數據類型的null
值,可以表示額外的信息,如:遠程調用失敗,異常退出。
DO
/DTO
/VO
等POJO
類時,不要設定任何屬性默認值。POJO
類的gmtCreate
默認值爲new Date()
,可是這個屬性在數據提取時並無置入具體值,在更新其它字段時又附帶更新了此字段,致使建立時間被修改爲當前時間。【強制】序列化類新增屬性時,請不要修改serialVersionUID
字段,避免反序列失敗;若是徹底不兼容升級,避免反序列化混亂,那麼請修改serialVersionUID
值。
說明:注意
serialVersionUID
不一致會拋出序列化運行時異常。
init
方法中。【強制】POJO
類必須寫toString
方法。使用IDE中的工具:source> generate toString
時,若是繼承了另外一個POJO
類,注意在前面加一下super.toString
。
說明:在方法執行拋出異常時,能夠直接調用
POJO
的toString()
方法打印其屬性值,便於排查問題。
【強制】禁止在POJO類
中,同時存在對應屬性xxx
的isXxx()
和getXxx()
方法。
說明:框架在調用屬性
xxx
的提取方法時,並不能肯定哪一個方法必定是被優先調用到。
String
的split
方法獲得的數組時,需作最後一個分隔符後有無內容的檢查,不然會有拋IndexOutOfBoundsException
的風險。String str = "a,b,c,,"; String[] ary = str.split(","); // 預期大於 3 ,結果是 3 System.out.println(ary.length);
【推薦】 類內方法定義的順序依次是:公有方法或保護方法 > 私有方法 > getter
/ setter
方法。
說明:公有方法是類的調用者和維護者最關心的方法,首屏展現最好;保護方法雖然只是子類關心,也多是"模板設計模式"下的核心方法;而私有方法外部通常不須要特別關心,是一個黑盒實現;由於承載的信息價值較低,全部
Service
和DAO
的getter
/setter
方法放在類體最後。
setter
方法中,參數名稱與類成員變量名稱一致
,this.成員名 = 參數名
。在getter
/setter
方法中,不要增長業務邏輯
,增長排查問題的難度。public Integer getData() { if (condition) { return this.data + 100; } else { return this.data - 100; } }
【推薦】循環體內,字符串的鏈接方式,使用StringBuilder
的append
方法進行擴展。
說明:下例中,反編譯出的字節碼文件顯示每次循環都會
new
出一個StringBuilder
對象,而後進行append
操做,最後經過toString
方法返回String
對象,形成內存資源浪費。
反例:
String str = "start"; for (int i = 0; i < 100; i++) { str = str + "hello"; }
final
能夠聲明類、成員變量、方法、以及本地變量,下列狀況使用final
關鍵字:String
類。POJO
類的setter
方法。final
描述能夠強制從新定義一個變量,方便更好Object
的clone
方法來拷貝對象。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
成員方法或成員變量,刪除一下,不得手心冒點汗嗎?變量像本身的小孩,儘可能在本身的視線內,變量做用域太大,無限制的處處跑,那麼你會擔憂的。
【強制】關於hashCode
和equals
的處理,遵循以下規則:
1 )只要重寫equals
,就必須重寫hashCode
。
2 )由於Set
存儲的是不重複的對象,依據hashCode
和equals
進行判斷,因此Set
存儲的對象必須重寫這兩個方法。
3 )若是自定義對象做爲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
,其它數組元素保持原值,所以最好將方法入參數組大小定義
與集合元素個數一致
。
正例:
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
錯誤。【強制】使用工具類Arrays.asList()
把數組轉換成集合時,不能使用其修改集合相關的方法,它的add/remove/clear
方法會拋出UnsupportedOperationException
異常。
說明:
asList
的返回對象是一個Arrays
內部類,並無實現集合的修改方法。Arrays.asList
體現的是適配器模式,只是轉換接口,後臺的數據還是數組。
String[] str = new String[] { "you", "wu" }; List list = Arrays.asList(str); //第一種狀況: list.add("yangguanbao"); //運行時異常。 //第二種狀況: str[0] = "gujin"; //那麼list.get(0)也會隨之修改。
【強制】泛型通配符<? extends T>
來接收返回的數據,此寫法的泛型集合不能使用add
方法,而<? super T>
不能使用get
方法,做爲接口調用賦值時易出錯。
說明:擴展說一下
PECS(Producer Extends Consumer Super)
原則:第1、頻繁往外讀取內容的,適合用<? extends T>
。第2、常常往裏插入的,適合用<? super T>
。
foreach
循環裏進行元素的remove
/add
操做。remove
元素請使用Iterator
方式,若是併發操做,須要對Iterator
對象加鎖。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(); } }
反例 ```java for (String item : list) { if ("1".equals(item)) { list.remove(item); } }
說明:以上代碼的執行結果確定會出乎你們的意料,那麼試一下把"1"換成"2",會是一樣的結果嗎?
【強制】 在JDK 7
版本及以上,Comparator
實現類要知足以下三個條件,否則Arrays.sort
,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; } };
【推薦】集合泛型定義時,在JDK7
及以上,使用diamond
語法或全省略。
說明:菱形泛型,即
diamond
,直接使用<>
來指代前邊已經指定的類型。
正例:
//diamond方式 HashMap<String, String> userCache = new HashMap<String, String>(16); //全省略方式 ArrayList<User> users = new ArrayList(10);
【推薦】集合初始化時,指定集合初始值大小
。
正例:說明:
HashMap
使用HashMap(int initialCapacity)
初始化。
initialCapacity = (須要存儲的元素個數 / 負載因子) + 1
。注意負載因子(即loaderfactor)默認爲0.75
,若是暫時沒法肯定初始值大小,請設置爲 16 (即默認值)。HashMap
須要放置 1024
個元素,因爲沒有設置容量初始大小,隨着元素不斷增長,容量 7
次被迫擴大,resize
須要重建hash表
,嚴重影響性能
。【推薦】使用entrySet
遍歷Map
類集合KV
,而不是keySet
方式進行遍歷。
正例:說明:
keySet
實際上是遍歷了2
次,一次是轉爲Iterator
對象,另外一次是從hashMap
中取出key
所對應的value
。而entrySet
只是遍歷了一次就把key
和value
都放到了entry
中,效率更高。若是是JDK 8
,使用Map.foreach
方法。
values()
返回的是V值
集合,是一個list
集合對象;keySet()
返回的是K值
集合,是一個Set集合
對象;entrySet()
返回的是K-V值
組合集合。【推薦】高度注意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異常
。
【參考】合理利用好集合的有序性(sort)
和穩定性(order)
,避免集合的無序性(unsort)
和不穩定性(unorder)
帶來的負面影響。
說明:有序性是指遍歷的結果是按某種比較規則依次排列的。穩定性指集合每次遍歷的元素次序是必定的。如:
ArrayList
是order
/unsort
;HashMap
是unorder
/unsort
;TreeSet
是order
/sort
。
【參考】利用Set
元素惟一的特性,能夠快速對一個集合進行去重
操做,避免使用Lis
t的contains
方法進行遍歷、對比、去重操做。
【強制】獲取單例對象須要保證線程安全,其中的方法也要保證線程安全。
說明:資源驅動類、工具類、單例工廠類都須要注意。
【強制】建立線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。
正例:
public class TimerTaskThread extends Thread { public TimerTaskThread() { super.setName("TimerTaskThread"); ... } }
【強制】線程資源必須經過線程池
提供,不容許在應用中自行顯式建立線程
。
說明:使用線程池的好處是減小在建立和銷燬線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題。若是不使用線程池,有可能形成系統建立大量同類線程而致使
消耗完內存
或者"過分切換
"的問題。
【強制】線程池不容許使用Executors
去建立,而是經過ThreadPoolExecutor
的方式,這樣的處理方式讓寫的同窗更加明確線程池的運行規則,規避資源耗盡的風險。
說明:
Executors
返回的線程池對象的弊端以下:
1 )FixedThreadPool
和SingleThreadPool
:
容許的請求隊列長度爲Integer.MAX_VALUE
,可能會堆積大量的請求,從而致使OOM
。
2 )CachedThreadPool
和ScheduledThreadPool
:
容許的建立線程數量爲Integer.MAX_VALUE
,可能會建立大量的線程,從而致使OOM
。
SimpleDateFormat
是線程不安全
的類,通常不要定義爲static
變量,若是定義爲 static
,必須加鎖,或者使用DateUtils
工具類。private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } };
> 說明:若是是`JDK 8` 的應用,能夠使用`Instant`代替`Date`,`LocalDateTime`代替`Calendar`,`DateTimeFormatter`代替`SimpleDateFormat`,官方給出的解釋:`simple beautiful stron immutable thread-safe`。
【強制】高併發時,同步調用應該去考量鎖的性能損耗
。能用無鎖數據結構,就不要用鎖;能鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。
說明:儘量使加鎖的代碼塊工做量儘量的小,避免在鎖代碼塊中調用
RPC
方法。
【強制】對多個資源、數據庫表、對象同時加鎖時,須要保持一致的加鎖順序
,不然可能會形成死鎖
。
說明:線程一須要對錶A、B、C依次所有加鎖後才能夠進行更新操做,那麼線程二的加鎖順序也必須是A、B、C,不然可能出現死鎖。
【強制】併發修改同一記錄時,避免更新丟失,須要加鎖。要麼在應用層加鎖,要麼在緩存加鎖,要麼在數據庫層使用樂觀鎖,使用version做爲更新依據
。
說明:若是每次訪問衝突機率小於
20%
,推薦使用樂觀鎖,不然使用悲觀鎖。樂觀鎖的重試次數不得小於3
次。
Timer
運行多個TimeTask
時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,使用ScheduledExecutorService
則沒有這個問題。【推薦】使用CountDownLatch
進行異步轉同步操做,每一個線程退出前必須調用countDown
方法,線程執行代碼注意catch異常
,確保countDown
方法被執行到,避免主線程沒法執行至await方法
,直到超時
才返回結果。
說明:注意,子線程拋出異常堆棧,不能在主線程
try-catch
到。
【推薦】避免Random實例
被多線程使用
,雖然共享該實例是線程安全
的,但會因競爭同一seed
致使的性能降低
。
正例:在說明:
Random
實例包括java.util.Random
的實例或者Math.random()
的方式。
JDK 7
以後,能夠直接使用API ThreadLocalRandom
,而在 JDK 7
以前,須要編碼保證每一個線程持有一個實例
。雙重檢查鎖(double-checked locking)
實現延遲初始化的優化問題隱患(可參考 The "Double-Checked Locking is Broken" Declaration
),推薦解決方案中較爲簡單一種(適用於JDK 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 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;
【強制】在高併發場景中,避免使用"等於"
判斷做爲中斷或退出的條件。
說明:若是併發控制沒有處理好,容易產生等值判斷被"
擊穿
"的狀況,使用大於或小於的區間判斷條件來代替。
反例:判斷剩餘獎品數量等於 0 時,終止發放獎品,但由於併發處理錯誤致使獎品數量瞬間變成了負數,這樣的話,活動沒法終止。
【推薦】表達異常的分支時,少用if-else
方式,這種方式能夠改寫成:
`java if (condition) { ... return obj; } ``` > 說明:若是非得使用if()...else if()...else...方式表達邏輯,**【強制】**避免後續代碼維護困難,請勿超過
3` 層。
正例:超過 3
層的 if-else
的邏輯判斷代碼能夠使用衛語句、策略模式、狀態模式等來實現,其中衛語句示例以下:
java 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; }
【推薦】除經常使用方法(如getXxx
/isXxx
)等外,不要在條件判斷中執行其它複雜的語句,將複雜邏輯判斷的結果賦值給一個有意義的布爾變量名,以提升可讀性。
說明:不少
if
語句內的邏輯至關複雜,閱讀者須要分析條件表達式的最終結果,才能明確什麼樣的條件執行什麼樣的語句,那麼,若是閱讀者分析邏輯表達式錯誤呢?
正例:
// 僞代碼以下 final boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed) { ... }
反例:
if ((file.open(fileName, "w") != null) && (...) || (...)) {
...
}
【推薦】循環體中的語句要考量性能,如下操做盡可能移至循環體外處理,如定義對象、變量、獲取數據庫鏈接,進行沒必要要的try-catch
操做(這個try-catch
是否能夠移至循環體外)。
【推薦】避免採用取反邏輯運算符。
正例:說明:取反邏輯不利於快速理解,而且取反邏輯寫法必然存在對應的正向邏輯寫法。
使用if (x < 628) 來表達 x 小於 628
。使用if (!(x >= 628)) 來表達 x 小於 628
。RPC
/API
/HTTP
接口。【參考】下列情形,不須要進行參數校驗:
1 )極有可能被循環調用的方法。但在方法說明裏必須註明外部參數檢查要求。
2 )底層調用頻度比較高的方法。畢竟是像純淨水過濾的最後一道,參數錯誤不太可能到底層纔會暴露問題。通常DAO
層與Service
層都在同一個應用中,部署在同一臺服務器中,因此DAO
的參數校驗,能夠省略。
3 )被聲明成private
只會被本身代碼所調用的方法,若是可以肯定調用方法的代碼傳入參數已經作過檢查或者確定不會有問題,此時能夠不校驗參數。
【強制】類、類屬性、類方法的註釋必須使用Javadoc規範
,使用/**內容*/
格式,不得使用// xxx
方式。
說明:在IDE編輯窗口中,
Javadoc
方式會提示相關注釋,生成Javadoc
能夠正確輸出相應註釋;在IDE中,工程調用方法時,不進入方法便可懸浮提示方法、參數、返回值的意義,提升閱讀效率。
【強制】全部的抽象方法(包括接口中的方法)必需要用Javadoc
註釋、除了返回值、參數、異常說明外,還必須指出該方法作什麼事情,實現什麼功能。
說明:對子類的實現要求,或者調用注意事項,請一併說明。
建立者
和建立日期
。//
註釋。方法內部多行註釋使用/* */
註釋,注意與代碼對齊。TCP
鏈接超時"解釋成"傳輸控制協議鏈接超時",理解反而費腦筋。【推薦】代碼修改的同時,註釋也要進行相應的修改,尤爲是參數、返回值、異常、核心邏輯等的修改。
說明:代碼與註釋更新不一樣步,就像路網與導航軟件更新不一樣步同樣,若是導航軟件嚴重滯後,就失去了導航的意義。
【參考】謹慎註釋掉代碼。在上方詳細說明,而不是簡單地註釋掉。若是無用,則刪除。
說明:代碼被註釋掉有兩種可能性:
1 )後續會恢復此段代碼邏輯。
2 )永久不用。前者若是沒有備註信息,難以知曉註釋動機。後者建議直接刪掉(代碼倉庫保存了歷史代碼)。
可以準確反應設計思想和代碼邏輯
;第2、可以描述業務含義,使別的程序員可以迅速瞭解到代碼背後的信息
。徹底沒有註釋的大段代碼對於閱讀者形同天書,註釋是給本身看的,即便隔很長時間,也能清晰理解當時的思路;註釋也是給繼任者看的,使其可以快速接替本身的工做。// put elephant into fridge put(elephant, fridge); //方法名put,加上兩個有意義的變量名elephant和fridge,已經說明了這是在幹什麼,語義清晰的代碼不須要額外的註釋。
待辦事宜(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
的方式來處理,好比:NullPointerException
、IndexOutOfBoundsException
等等。
正例:說明:沒法經過預檢查的異常除外,好比,在解析字符串形式的數字時,不得不經過catch NumberFormatException來實現。
if (obj != null) {...}
try { obj.method(); } catch (NullPointerException e) {...}
【強制】異常不要用來作流程控制,條件控制。
說明:異常設計的初衷是解決程序運行中的各類意外狀況,且異常的處理效率比條件判斷方式要低不少。
【強制】catch
時請分清穩定代碼
和非穩定代碼
,穩定代碼指的是不管如何不會出錯的代碼。對於非穩定代碼的catch
儘量進行區分異常類型,再作對應的異常處理。
正例:用戶註冊的場景中,若是用戶輸入非法字符,或用戶名稱已存在,或用戶輸入密碼過於簡單,在程序上做出分門別類的判斷,並提示給用戶。說明:對大段代碼進行
try-catch
,使程序沒法根據不一樣的異常作出正確的應激反應,也不利於定位問題,這是一種不負責任的表現。
捕獲異常
是爲了處理
它,不要捕獲了卻什麼都不處理而拋棄之,若是不想處理它,請將該異常拋給它的調用者。最外層的業務使用者,必須處理異常,將其轉化爲用戶能夠理解的內容。try
塊放到了事務代碼中,catch
異常後,若是須要回滾事務
,必定要注意手動回滾事務
。【強制】finally
塊必須對資源對象、流對象進行關閉,有異常也要作try-catch
。
說明:若是
JDK 7
及以上,能夠使用try-with-resources
方式。
【強制】不要在finally
塊中使用return
。
說明:
finally
塊中的return
返回後方法結束執行,不會再執行try
塊中的return
語句。
【強制】捕獲異常與拋異常,必須是徹底匹配
,或者捕獲異常是拋異常的父類
。
說明:若是預期對方拋的是繡球,實際接到的是鉛球,就會產生意外狀況。
【推薦】方法的返回值能夠爲null
,不強制返回空集合,或者空對象等,必須添加註釋充分說明什麼狀況下會返回null
值。
說明:本手冊明確防止
NPE
是調用者的責任。即便被調用方法返回空集合或者空對象,對調用者來講,也並不是高枕無憂,必須考慮到遠程調用失敗、序列化失敗、運行時異常等場景返回null
的狀況。
NPE
,是程序員的基本修養,注意NPE
產生的場景:return
包裝數據類型的對象時,自動拆箱有可能產生NPE
。public int f() { return Integer對象}, 若是爲null,自動解箱拋NPE。
null
。isNotEmpty
,取出的數據元素也可能爲null
。NPE
。Session中
獲取的數據,建議NPE
檢查,避免空指針
。obj.getA().getB().getC()
;一連串調用,易產生NPE
。JDK8
的Optional
類來防止NPE
問題。unchecked
/ checked
異常,避免直接拋出new RuntimeException()
,更不容許拋出Exception
或者Throwable
,應使用有業務含義的自定義異常。推薦業界已定義過的自定義異常,如:DAOException
/ ServiceException
等。【參考】對於公司外的http
/api
開放接口必須使用"錯誤碼
";而應用內部推薦異常拋出;跨應用間RPC
調用優先考慮使用Result
方式,封裝isSuccess()
方法、"錯誤碼"、"錯誤簡短信息"。
說明:關於
RPC
方法返回方式使用Result
方式的理由:
new
自定義異常,加入本身的理解的error message
,對於調用端解決問題的幫助不會太多。若是加了棧信息,在頻繁調用出錯的狀況下,數據序列化和傳輸的性能損耗也是問題。【參考】避免出現重複的代碼(Don’t Repeat Yourself
),即DRY
原則。
說明:隨意複製和粘貼代碼,必然會致使代碼的重複,在之後須要修改時,須要修改全部的副本,容易遺漏。必要時抽取共性方法,或者抽象公共類,甚至是組件化。
正例:一個類中有多個public
方法,都須要進行數行相同的參數校驗操做,這個時候請抽取:
``java
private boolean checkParam(DTO dto) {...}
```
Log4j
、Logback
)中的API
,而應依賴使用日誌框架SLF4J
中的API
,使用門面模式
的日誌框架,有利於維護和各個類的日誌處理方式統一。java import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(Abc.class);
15
天,由於有些異常具有以"周"爲頻次發生的特色。【強制】應用中的擴展日誌(如打點、臨時監控、訪問日誌等)命名方式:appName_logType_logName.log
。
logType
:日誌類型,如stats
/monitor
/access
等;logName
:日誌描述。這種命名的好處:經過文件名就可知道日誌文件屬於什麼應用,什麼類型,什麼目的,也有利於歸類查找。
正例:mppserver
應用中單獨監控時區轉換異常,如:ppserver_monitor_timeZoneConvert.log
說明:推薦對日誌進行分類,如將錯誤日誌和業務日誌分開存放,便於開發人員查看,也便於經過日誌對系統進行及時監控。
【強制】對trace
/debug
/info
級別的日誌輸出,必須使用條件輸出形式或者使用佔位符的方式。
說明:
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);
log4j.xml
中設置additivity=false
。<logger name="com.taobao.dubbo.config" additivity="false">
案發現場信息
和異常堆棧信息
。若是不處理,那麼經過關鍵字throws
往上拋出。logger.error(各種參數或者對象toString() + "_" + e.getMessage(), e);
【推薦】謹慎地記錄日誌。生產環境禁止輸出debug
日誌;有選擇地輸出info
日誌;若是使用warn
來記錄剛上線時的業務行爲信息,必定要注意日誌輸出量的問題,避免把服務器磁盤撐爆,並記得及時刪除這些觀察日誌。
說明:大量地輸出無效日誌,不利於系統性能提高,也不利於快速定位錯誤點。記錄日誌時請思考:這些日誌真的有人看嗎?看到這條日誌你能作什麼?能不能給問題排查帶來好處?
【推薦】能夠使用warn
日誌級別來記錄用戶輸入參數錯誤的狀況,避免用戶投訴時,無所適從。如非必要,請不要在此場景打出error
級別,避免頻繁報警。
說明:注意日誌輸出的級別,error級別只記錄系統邏輯出錯、異常或者重要的錯誤信息。
【推薦】儘可能用英文來描述日誌錯誤信息,若是日誌中的錯誤信息用英文描述不清楚的話使用中文描述便可,不然容易產生歧義。國際化團隊或海外部署的服務器因爲字符集問題,【強制】使用全英文來註釋和描述日誌錯誤信息。
【強制】好的單元測試必須遵照AIR
原則。
說明:單元測試在線上運行時,感受像空氣(AIR)同樣並不存在,但在測試質量的保障上,倒是很是關鍵的。好的單元測試宏觀上來講,具備自動化、獨立性、可重複執行的特色。
System.out
來進行人肉驗證,必須使用assert
來驗證。獨立性
。爲了保證單元測試穩定可靠且便於維護,單元測試用例之間決不能互相調用,也不能依賴執行的前後次序
。check in
時單元測試都會被執行。若是單測對外部環境(網絡、服務、中間件等)有依賴,容易致使持續集成機制的不可用。SUT
的依賴改爲注入,在測試時用spring
這樣的DI框架注入一個本地(內存)實現或者Mock
實現。【強制】對於單元測試,要保證測試粒度足夠小,有助於精肯定位問題。單測粒度至可能是類級別,通常是方法級別。
說明:只有測試粒度小才能在出錯時儘快定位到出錯位置。單測不負責檢查跨類或者跨系統的交互邏輯,那是集成測試的領域。
【強制】核心業務、核心應用、核心模塊的增量代碼確保單元測試經過。
說明:新增代碼及時補充單元測試,若是新增代碼影響了原有單元測試,請及時修正。
【強制】單元測試代碼必須寫在以下工程目錄:src/test/java
,不容許寫在業務代碼目錄下。
說明:源碼構建時會跳過此目錄,而單元測試框架默認是掃描此目錄。
【推薦】單元測試的基本目標:語句覆蓋率達到70%
;核心模塊的語句覆蓋率和分支覆蓋率都要達到100%
說明:在工程規約的應用分層中提到的
DAO
層,Manager
層,可重用度高的Service
,都應該進行單元測試。
BCDE
原則,以保證被測試模塊的交付質量。
Border
,邊界值測試,包括循環邊界、特殊取值、特殊時間點、數據順序等。Correct
,正確的輸入,並獲得預期的結果。Design
,與設計文檔相結合,來編寫單元測試。Error
,強制錯誤信息輸入(如:非法數據、異常流程、非業務容許輸入等),並獲得預期的結果。【推薦】和數據庫相關的單元測試,能夠設定自動回滾機制
,不給數據庫形成髒數據
。或者對單元測試產生的數據有明確的先後綴標識。
正例:在
RDC
內部單元測試中,使用RDC_UNIT_TEST_
的前綴標識數據。
開發人員
須要和測試人員
一塊兒肯定單元測試範圍
,單元測試最好覆蓋全部測試用例。【強制】隸屬於用戶我的的頁面或者功能必須進行權限控制校驗。
說明:防止沒有作水平權限校驗就可隨意訪問、修改、刪除別人的數據,好比查看他人的私信內容、修改他人的訂單。
【強制】用戶敏感數據禁止直接展現,必須對展現數據進行脫敏。
說明:中國大陸我的手機號碼顯示爲:158****9119,隱藏中間 4 位,防止隱私泄露。
SQL
參數嚴格使用參數綁定或者METADATA
字段值限定,防止SQL注入
,禁止字符串拼接SQL
訪問數據庫。【強制】用戶請求傳入的任何參數必須作有效性驗證。
說明:忽略參數校驗可能致使:
order by
致使數據庫慢查詢SQL注入
正則輸入源串拒絕服務ReDoS
說明:
Java
代碼用正則來驗證客戶端的輸入,有些正則寫法驗證普通用戶輸入沒有問題,可是若是攻擊人員使用的是特殊構造的字符串來驗證,有可能致使死循環的結果。
HTML
頁面輸出未經安全過濾或未正確轉義的用戶數據。【強制】表單、AJAX
提交必須執行CSRF
安全驗證。
說明:
CSRF(Cross-site request forgery)
跨站請求僞造是一類常見編程漏洞。對於存在CSRF
漏洞的應用/網站,攻擊者能夠事先構造好URL
,只要受害者用戶一訪問,後臺便在用戶不知情的狀況下對數據庫中用戶參數進行相應修改。
【強制】在使用平臺資源,譬如短信、郵件、電話、下單、支付,必須實現正確的防重放的機制,如數量限制、疲勞度控制、驗證碼校驗,避免被濫刷而致使資損。
說明:如註冊時發送驗證碼到手機,若是沒有限制次數和頻率,那麼能夠利用此功能騷擾到其它用戶,並形成短信平臺資源浪費。
【推薦】發貼、評論、發送即時消息等用戶生成內容的場景必須實現防刷、文本內容違禁詞過濾等風控策略。
【強制】表達是與否概念的字段,必須使用is_xxx
的方式命名,數據類型是unsigned tinyint( 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類名也是單數形式,符合表達習慣。
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
必爲主鍵,類型爲bigint
unsigned
、單表時自增
、步長爲1
。gmt_create
,gmt_modifie
d的類型均爲datetime
類型,前者如今時表示主動建立,後者過去分詞表示被動更新。
alipay_task
/ force_project
/ trade_config
varchar
超長字段,更不能是text
字段。【推薦】單錶行數超過 500
萬行或者單表容量超過 2
GB,才推薦進行分庫分表。
說明:若是預計三年後的數據量根本達不到這個級別,請不要在建立表時就分庫分表。
【參考】合適的字符存儲長度,不但節約數據庫表空間、節約索引存儲,更重要的是提高檢索速度。
正例:以下表,其中無符號值能夠避免誤存負數,且擴大了表示範圍。
對象 | 年齡區間 | 類型 | 字節 | 表示範圍 |
---|---|---|---|---|
人 | 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。須要join的字段,數據類型必須絕對一致
;多表關聯查詢時,保證被關聯的字段須要有索引
。
說明:即便雙表join也要注意表索引、SQL性能。
【強制】在varchar
字段上創建索引時,必須指定索引長度
,不必對全字段創建索引,根據實際文本區分度決定索引長度便可。
說明:索引的長度與區分度是一對矛盾體,通常對字符串類型數據,長度爲
20
的索引,區分度會高達90%
以上,能夠使用count(distinct left(列名, 索引長度))/count(*)
的區分度來肯定。
【強制】頁面搜索嚴禁左模糊或者全模糊,若是須要請走搜索引擎來解決。
說明:索引文件具備
B-Tree
的最左前綴匹配特性,若是左邊的值未肯定,那麼沒法使用此索引。
order by
的場景,請注意利用索引的有序性
。order by
最後的字段是組合索引的一部分,而且放在索引組合順序的最後,避免出現file_sort
的狀況,影響查詢性能。where a=? and b=? order by c;
索引:a_b_cWHERE 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
【推薦】 SQL性能優化的目標:至少要達到 range
級別,要求是ref
級別,若是能夠是consts
最好。
反例:說明:
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(列名)
或count(常量)
來替代count(*)
,count(*)
是SQL 92
定義的標準統計行數的語法,跟數據庫無關,跟NULL
和非NULL
無關。
說明:
count(*)
會統計值爲NULL的行
,而count(列名)
不會統計此列爲NULL值的行
。
count(distinct col)
計算該列除NULL
以外的不重複行數,注意 count(distinct col1, col 2 )
若是其中一列全爲NULL
,那麼即便另外一列有不一樣的值,也返回爲 0
。NULL
時,count(col)
的返回結果爲 0 ,但sum(col)
的返回結果爲NULL
,所以使用sum()
時需注意NPE問題。sum
的NPE
問題:SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table;
【強制】使用ISNULL()
來判斷是否爲NULL
值。
說明:NULL與任何值的直接比較都爲
NULL
。
1 )NULL<>NULL
的返回結果是NULL
,而不是false
。
2 )NULL=NULL
的返回結果是NULL
,而不是true
。
3 )NULL<>1
的返回結果是NULL
,而不是true
。
count
爲 0
應直接返回,避免執行後面的分頁語句。【強制】不得使用外鍵與級聯,一切外鍵概念
必須在應用層
解決。
說明:以學生和成績的關係爲例,學生表中的
student_id
是主鍵,那麼成績表中的student_id
則爲外鍵。若是更新學生表中的student_id
,同時觸發成績表中的student_id
更新,即爲級聯更新
。外鍵與級聯更新適用於單機低併發,不適合分佈式、高併發集羣;級聯更新是強阻塞,存在數據庫更新風暴的風險;外鍵影響數據庫的插入速度。
禁止使用存儲過程
,存儲過程難以調試和擴展,更沒有移植性。select
,避免出現誤刪除,確認無誤才能執行更新語句。【推薦】in
操做能避免則避免,若實在避免不了,須要仔細評估in
後邊的集合元素數量,控制在 1000
個以內。
【參考】若是有國際化須要,全部的字符存儲與表示,均以utf-8
編碼,注意字符統計函數的區別。
說明:
SELECT LENGTH("輕鬆工做"); 返回爲 12
SELECT CHARACTER_LENGTH("輕鬆工做"); 返回爲 4
若是須要存儲表情,那麼選擇utf8mb4
來進行存儲,注意它與utf-8
編碼的區別。
【參考】TRUNCATE TABLE
比 DELETE
速度快,且使用的系統和事務日誌資源少,但TRUNCATE
無事務且不觸發trigger
,有可能形成事故,故不建議在開發代碼中使用此語句。
說明:
TRUNCATE TABLE
在功能上與不帶WHERE
子句的DELETE
語句相同。
【強制】在表查詢中,一概不要使用 *
做爲查詢的字段列表,須要哪些字段必須明確寫明。
說明:
1 )增長查詢分析器解析成本。
2 )增減字段容易與resultMap
配置不一致。
3 )無用字段增長網絡消耗,尤爲是text
類型的字段。
【強制】POJO
類的布爾屬性不能加is
,而數據庫字段必須加is_
,要求在resultMap
中進行字段與屬性之間的映射。
說明:參見定義
POJO
類以及數據庫字段定義規定,在<resultMap>
中增長映射,是必須的。在MyBatis Generator
生成的代碼中,須要進行對應的修改。
【強制】不要用resultClass
當返回參數,即便全部類屬性名與數據庫字段一一對應,也須要定義;反過來,每個表也必然有一個POJO
類與之對應。
說明:配置映射關係,使字段與
DO
類解耦,方便維護。
sql.xml
配置參數使用:#{}
,#param#
不要使用${}
此種方式容易出現SQL注入
。【強制】iBATIS
自帶的queryForList(String statementName,int start,int size)
不推薦使用。
說明:其實現方式是在數據庫取到
statementName
對應的SQL
語句的全部記錄,再經過subList
取start
,size
的子集合。
正例:
Map<String, Object> map = new HashMap<>(); map.put("start", start); map.put("size", size);
【強制】不容許直接拿HashMap
與Hashtable
做爲查詢結果集的輸出。
說明:
resultClass="Hashtable"
,會置入字段名和屬性值,可是值的類型不可控。
【強制】更新數據表記錄時,必須同時更新記錄對應的gmt_modified
字段值爲當前時間段,都進行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
渲染,移動端展現等。Service
層通用能力的下沉,如緩存方案、中間件通用處理;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
向模板渲染引擎層傳輸的對象。2
個參數的查詢封裝,禁止使用Map
類來傳輸。【強制】定義GAV聽從如下規則:
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
版本(安全包除外)。
說明:不依賴
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>
裏的依賴都會自動引入,並默認被全部的子項目繼承。
【參考】爲避免應用二方庫的依賴衝突問題,二方庫發佈者應當遵循如下原則:
1 )精簡可控原則。移除一切沒必要要的API
和依賴,只包含 Service API
、必要的領域模型對象、Utils
類、常量、枚舉等。若是依賴其它二方庫,儘可能是provided
引入,讓二方庫使用者去依賴具體版本號;無log
具體實現,只依賴日誌框架。
2 )穩定可追溯原則。每一個版本的變化應該被記錄,二方庫由誰維護,源碼在哪裏,都須要能方便查到。除非用戶主動升級版本,不然公共二方庫的行爲不該該發生變化。
【推薦】高併發服務器建議調小TCP
協議的time_wait
超時時間。
正例:在說明:操做系統默認
240
秒後,纔會關閉處於time_wait
狀態的鏈接,在高併發訪問下,服務器端會由於處於time_wait
的鏈接數太多,可能沒法創建新的鏈接,因此須要在服務器上調小此等待值。
linu
x服務器上請經過變動/etc/sysctl.conf
文件去修改該缺省值(秒):net.ipv4.tcp_fin_timeout = 30
【推薦】調大服務器所支持的最大文件句柄數
(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
維護不一致的問題和潛在的安全風險。
【強制】存儲方案和底層數據結構的設計得到評審一致經過,並沉澱成爲文檔。
說明:有缺陷的底層數據結構容易致使系統風險上升,可擴展性降低,重構成本也會因歷史數據遷移和系統平滑過渡而陡然增長,因此,存儲方案和數據結構須要認真地進行設計和評審,生產環境提交執行後,須要進行
double check
。
正例:評審內容包括存儲介質選型、表結構設計可否知足技術方案、存取性能和存儲空間可否知足業務發展、表或字段之間的辯證關係、字段名稱、字段類型、索引等;數據結構變動(如在原有表中新增字段)也須要進行評審經過後上線。
User
超過一類而且相關的User Case
超過 5
個,使用用例圖
來表達更加清晰的結構化需求。【強制】若是某個業務對象的狀態超過 3
個,使用狀態圖
來表達而且明確狀態變化的各個觸發條件。
正例:淘寶訂單狀態有已下單、待付款、已付款、待發貨、已發貨、已收貨等。好比已下單與已收貨這兩種狀態之間是不可能有直接轉換關係的。說明:
狀態圖
的核心是對象狀態,首先明確對象有多少種狀態,而後明確兩兩狀態之間是否存在直接轉換關係,再明確觸發狀態轉換的條件是什麼。
【強制】若是系統中某個功能的調用鏈路上的涉及對象超過 3
個,使用時序圖
來表達而且明確各調用環節的輸入與輸出。
說明:
時序圖
反映了一系列對象間的交互與協做關係,清晰立體地反映系統的調用縱深鏈路。
【強制】若是系統中模型類超過 5
個,而且存在複雜的依賴關係,使用類圖
來表達而且明確類之間的關係。
說明:
類圖
像建築領域的施工圖,若是搭平房,可能不須要,但若是建造螞蟻Z空間大樓,確定須要詳細的施工圖。
【強制】若是系統中超過 2
個對象之間存在協做關係,而且須要表示複雜的處理流程,使用活動圖
來表示。
說明:活動圖是流程圖的擴展,增長了可以體現協做關係的對象泳道,支持表示併發等。
異常流程
與業務邊界
。【推薦】類在設計與實現時要符合單一原則。
說明:單一原則最易理解倒是最難實現的一條規則,隨着系統演進,不少時候,忘記了類設計的初衷。
【推薦】謹慎使用繼承的方式來進行擴展,優先使用聚合
/組合
的方式來實現。
說明:不得已使用繼承的話,必須符合
里氏代換
原則,此原則說父類可以出現的地方子類必定可以出現,好比,"把錢交出來",錢的子類美圓、歐元、人民幣等均可以出現。
【推薦】系統設計時,根據依賴倒置
原則,儘可能依賴抽象類與接口,有利於擴展與維護。
說明:低層次模塊依賴於高層次模塊的抽象,方便系統間的解耦。
【推薦】系統設計時,注意對擴展開放
,對修改閉合
。
說明:極端狀況下,交付的代碼都是不可修改的,同一業務域內的需求變化,經過模塊或類的擴展來實現。
【推薦】系統設計階段,共性業務或公共行爲抽取出來公共模塊、公共配置、公共類、公共方法等,避免出現重複代碼或重複配置的狀況。
說明:隨着代碼的重複次數不斷增長,維護成本指數級上升。
【推薦】避免以下誤解:敏捷開發 = 講故事 + 編碼 + 發佈
。
反例:某團隊爲了業務快速發展,敏捷成了產品經理催進度的藉口,系統中均是勉強能運行但像麪條同樣的代碼,可維護性和可擴展性極差,一年以後,不得不進行大規模重構,得不償失。說明:敏捷開發是快速交付迭代可用的系統,省略多餘的設計方案,摒棄傳統的審批流程,但核心關鍵點上的必要設計和文檔沉澱是須要的。
【參考】系統設計主要目的是明確需求
、理順邏輯
、後期維護
,次要目的用於指導編碼。
說明:避免爲了設計而設計,系統
設計文檔
有助於後期的系統維護,因此設計結果須要進行分類歸檔保存。
【參考】設計的本質就是識別和表達系統難點
,找到系統的變化點,並隔離變化點。
說明:世間衆多設計模式目的是相同的,即隔離系統變化點。
【參考】系統架構設計的目的:
肯定系統邊界
。肯定系統在技術層面上的作與不作。
肯定系統內模塊之間的關係
。肯定模塊之間的依賴關係及模塊的宏觀輸入與輸出。
肯定指導後續設計與演化的原則
。使後續的子系統或模塊設計在規定的框架內繼續演化。
肯定非功能性需求
。非功能性需求是指安全性、可用性、可擴展性等。
POJO(Plain Ordinary Java Object)
: 在本手冊中,POJO
專指只有setter
/ getter
/ toString
的簡單類,包括DO
/DTO
/BO
/VO
等。GAV(GroupId、ArtifactctId、Version
): Maven
座標,是用來惟一標識jar
包。OOP(Object Oriented Programming)
: 本手冊泛指類、對象的編程處理方式。ORM(Object Relation Mapping)
: 對象關係映射,對象領域模型與底層數據之間的轉換,本文泛指iBATIS
, mybatis
等框架。NPE(java.lang.NullPointerException)
: 空指針異常。SOA(Service-Oriented Architecture)
: 面向服務架構,它能夠根據需求經過網絡對鬆散以耦以的粗粒度應用組件進行分佈式部署、組合和使用,有利於提高組件可重用性,可維護性。IDE(Integrated Development Environment)
: 用於提供程序開發環境的應用程序,通常包括代碼編輯器、編譯器、調試器和圖形用戶界面等工具,本《手冊》泛指IntelliJ IDEA
和eclipse
。OOM(Out Of Memory)
: 源於java.lang.OutOfMemoryError
,當JVM
沒有足夠的內存來爲對象分配空間而且垃圾回收器也沒法回收空間時,系統出現的嚴重情況。jar
包)。jar
包)。jar
包)。