1、 命名風(fēng)格
1.1、代碼中的命名均不能以下劃線或美元符號(hào)開始,也不能以下劃線或美元符號(hào)結(jié)束。
反例:
_name, $name, __name
1.2、代碼中的命名嚴(yán)禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。
說明:正確的英文拼寫和語(yǔ)法可以讓閱讀者易于理解,避免歧義。注意,純拼音命名方式更要避免采用。
正例:renminbi / alibaba / taobao / youku / hangzhou 等國(guó)際通用的名稱,可視同英文。
反例:DaZhePromotion [打折] / getPingfenByName() [評(píng)分] / int 某變量 = 3
1.3、類名使用 UpperCamelCase 風(fēng)格,但以下情形例外:DO / BO / DTO / VO / AO/ PO / UID 等。
正例:JavaServerlessPlatform / UserDO / XmlService / TcpUdpDeal / TaPromotion
反例:javaserverlessplatform / UserDo / XMLService / TCPUDPDeal / TAPromotion
1.4、方法名、參數(shù)名、成員變量、局部變量都統(tǒng)一使用 lowerCamelCase 風(fēng)格,必須遵從駝峰形式。
正例:localValue / getHttpMessage() / inputUserId
1.5、常量命名全部大寫,單詞間用下劃線隔開,力求語(yǔ)義表達(dá)完整清楚,不要嫌名字長(zhǎng)。
正例:MAX_STOCK_COUNT / CACHE_EXPIRED_TIME
反例:MAX_COUNT / EXPIRED_TIME
1.6、抽象類命名使用 Abstract 或 Base 開頭;異常類命名使用 Exception 結(jié)尾;測(cè)試類命名以它要測(cè)試的類的名稱開始,以 Test 結(jié)尾。
1.7、類型與中括號(hào)緊挨相連來(lái)表示數(shù)組。
正例:定義整形數(shù)組 int[] arrayDemo;
反例:在 main 參數(shù)中,使用 String args[]來(lái)定義。
1.8、POJO 類中布爾類型變量都不要加 is 前綴,否則部分框架解析會(huì)引起序列化錯(cuò)誤。
說明:在本文 MySQL 規(guī)約中的建表約定第一條,表達(dá)是與否的值采用 is_xxx 的命名方式,所以,需要在設(shè)置從 is_xxx 到 xxx 的映射關(guān)系。
反例:定義為基本數(shù)據(jù)類型 Boolean isDeleted 的屬性,它的方法也是 isDeleted(),RPC 框架在反向解析的時(shí)候,“誤以為”對(duì)應(yīng)的屬性名稱是 deleted,導(dǎo)致屬性獲取不到,進(jìn)而拋出異常。
1.9、包名統(tǒng)一使用小寫,點(diǎn)分隔符之間有且僅有一個(gè)自然語(yǔ)義的英語(yǔ)單詞。包名統(tǒng)一使用單數(shù)形式,但是類名如果有復(fù)數(shù)含義,類名可以使用復(fù)數(shù)形式。
正例:應(yīng)用工具類包名為 com.alibaba.ai.util、類名為 MessageUtils(此規(guī)則參考 spring 的框架結(jié)構(gòu))
1.10、避免在子父類的成員變量之間、或者不同代碼塊的局部變量之間采用完全相同的命名,使可讀性降低。
說明:子類、父類成員變量名相同,即使是 public 類型的變量也是能夠通過編譯,而局部變量在同一方法內(nèi)的不同代碼塊中同名也是合法的,但是要避免使用。對(duì)于非 setter/getter 的參數(shù)名稱也要避免與成員變量名稱相同。
反例:
public class ConfusingName {
public int age; // 非 setter/getter 的參數(shù)名稱,不允許與本類成員變量同名
public void getData(String alibaba) {
if(condition) {
final int money = 531; // ...
}
for (int i = 0; i < 10; i++) {
// 在同一方法體中,不允許與其它代碼塊中的 money 命名相同
final int money = 615; // ...
}
}
}
class Son extends ConfusingName {
// 不允許與父類的成員變量名稱相同 public int age;
}
1.11、杜絕完全不規(guī)范的縮寫,避免望文不知義。
反例:AbstractClass“縮寫”命名成 AbsClass;condition“縮寫”命名成 condi,此類隨意縮寫嚴(yán)重降低了代碼的可閱讀性。
1.12、為了達(dá)到代碼自解釋的目標(biāo),任何自定義編程元素在命名時(shí),使用盡量完整的單詞組合來(lái)表達(dá)其意。
正例:在 JDK 中,表達(dá)原子更新的類名為:AtomicReferenceFieldUpdater。
反例:int a 的隨意命名方式。
1.13、在常量與變量的命名時(shí),表示類型的名詞放在詞尾,以提升辨識(shí)度。
正例:startTime / workQueue / nameList / TERMINATED_THREAD_COUNT
反例:startedAt / QueueOfWork / listName / COUNT_TERMINATED_THREAD
1.14、如果模塊、接口、類、方法使用了設(shè)計(jì)模式,在命名時(shí)需體現(xiàn)出具體模式。
說明:將設(shè)計(jì)模式體現(xiàn)在名字中,有利于閱讀者快速理解架構(gòu)設(shè)計(jì)理念。
正例:
public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;
1.15、接口類中的方法和屬性不要加任何修飾符號(hào)(public 也不要加),保持代碼的簡(jiǎn)潔性,并加上有效的 Javadoc 注釋。盡量不要在接口里定義變量,如果一定要定義變量,肯定是與接口方法相關(guān),并且是整個(gè)應(yīng)用的基礎(chǔ)常量。
正例:
接口方法簽名 void commit();
接口基礎(chǔ)常量 String COMPANY = “alibaba”;
反例:接口方法定義 public abstract void f();
說明:JDK8 中接口允許有默認(rèn)實(shí)現(xiàn),那么這個(gè) default 方法,是對(duì)所有實(shí)現(xiàn)類都有價(jià)值的默認(rèn)實(shí)現(xiàn)。
1.16、接口和實(shí)現(xiàn)類的命名有兩套規(guī)則:
對(duì)于 Service 和 DAO 類,基于 SOA 的理念,暴露出來(lái)的服務(wù)一定是接口,內(nèi)部的實(shí)現(xiàn)類用 Impl 的后綴與接口區(qū)別。
正例:CacheServiceImpl 實(shí)現(xiàn) CacheService 接口。
1.17、如果是形容能力的接口名稱,取對(duì)應(yīng)的形容詞為接口名(通常是–able 的形容詞)。
正例:AbstractTranslator 實(shí)現(xiàn) Translatable 接口。
1.18、枚舉類名帶上 Enum 后綴,枚舉成員名稱需要全大寫,單詞間用下劃線隔開。
說明:枚舉其實(shí)就是特殊的類,域成員均為常量,且構(gòu)造方法被默認(rèn)強(qiáng)制是私有。
正例:枚舉名字為 ProcessStatusEnum 的成員名稱:SUCCESS / UNKNOWN_REASON。
1.19、各層命名規(guī)約:
Service/DAO 層方法命名規(guī)約
獲取單個(gè)對(duì)象的方法用 get 做前綴。
獲取多個(gè)對(duì)象的方法用 list 做前綴,復(fù)數(shù)形式結(jié)尾如:listObjects。
獲取統(tǒng)計(jì)值的方法用 count 做前綴。
插入的方法用 save/insert 做前綴。
刪除的方法用 remove/delete 做前綴。
修改的方法用 update 做前綴。
1.19、領(lǐng)域模型命名規(guī)約
數(shù)據(jù)對(duì)象:xxxDO,xxx 即為數(shù)據(jù)表名。
數(shù)據(jù)傳輸對(duì)象:xxxDTO,xxx 為業(yè)務(wù)領(lǐng)域相關(guān)的名稱。
展示對(duì)象:xxxVO,xxx 一般為網(wǎng)頁(yè)名稱。
POJO 是 DO/DTO/BO/VO 的統(tǒng)稱,禁止命名成 xxxPOJO。
2、 常量定義
2.1、不允許任何魔法值(即未經(jīng)預(yù)先定義的常量)直接出現(xiàn)在代碼中。
反例:
String key = “Id#taobao_” + tradeId;cache.put(key, value);// 緩存 get 時(shí),由于在代碼復(fù)制時(shí),漏掉下劃線,導(dǎo)致緩存擊穿而出現(xiàn)問題
2.2、在 long 或者 Long 賦值時(shí),數(shù)值后使用大寫的 L,不能是小寫的 l,小寫容易跟數(shù)字 1 混淆,造成誤解。
說明:Long a = 2l; 寫的是數(shù)字的 21,還是 Long 型的 2
2.3、不要使用一個(gè)常量類維護(hù)所有常量,要按常量功能進(jìn)行歸類,分開維護(hù)。
說明:大而全的常量類,雜亂無(wú)章,使用查找功能才能定位到修改的常量,不利于理解和維護(hù)。
正例:緩存相關(guān)常量放在類 CacheConsts 下;系統(tǒng)配置相關(guān)常量放在類 ConfigConsts 下。
2.4、常量的復(fù)用層次有五層:跨應(yīng)用共享常量、應(yīng)用內(nèi)共享常量、子工程內(nèi)共享常量、包內(nèi)共享常量、類內(nèi)共享常量。
跨應(yīng)用共享常量:放置在二方庫(kù)中,通常是 client.jar 中的 constant 目錄下。
應(yīng)用內(nèi)共享常量:放置在一方庫(kù)中,通常是子模塊中的 constant 目錄下。
反例:易懂變量也要統(tǒng)一定義成應(yīng)用內(nèi)共享常量,兩位工程師在兩個(gè)類中分別定義了“YES”的變量:
類 A 中:public static final String YES = “yes”;
類 B 中:public static final String YES = “y”;
A.YES.equals(B.YES),預(yù)期是 true,但實(shí)際返回為 false,導(dǎo)致線上問題。
子工程內(nèi)部共享常量:即在當(dāng)前子工程的 constant 目錄下。
包內(nèi)共享常量:即在當(dāng)前包下單獨(dú)的 constant 目錄下。
類內(nèi)共享常量:直接在類內(nèi)部 private static final 定義。
2.5、如果變量值僅在一個(gè)固定范圍內(nèi)變化用 enum 類型來(lái)定義。
說明:如果存在名稱之外的延伸屬性應(yīng)使用 enum 類型,下面正例中的數(shù)字就是延伸信息,表示一年中的第幾個(gè)季節(jié)。
正例:
public enum SeasonEnum {
SPRING(1),
SUMMER(2),
AUTUMN(3),
WINTER(4);
private int seq;
SeasonEnum(int seq) {
this.seq = seq;
}
public int getSeq() {
return seq;
}
}
}
3、 代碼格式
3.1、如果是大括號(hào)內(nèi)為空,則簡(jiǎn)潔地寫成{}即可,大括號(hào)中間無(wú)需換行和空格;如果是非空代碼塊則:
左大括號(hào)前不換行。
左大括號(hào)后換行。
右大括號(hào)前換行。
右大括號(hào)后還有 else 等代碼則不換行;表示終止的右大括號(hào)后必須換行。
3.2、左小括號(hào)和字符之間不出現(xiàn)空格;同樣,右小括號(hào)和字符之間也不出現(xiàn)空格;而左大括號(hào)前需要空格。詳見第 5 條下方正例提示。
反例:if (空格 a == b 空格)
3.3、if/for/while/switch/do 等保留字與括號(hào)之間都必須加空格。
3.4、任何二目、三目運(yùn)算符的左右兩邊都需要加一個(gè)空格。
說明:運(yùn)算符包括賦值運(yùn)算符=、邏輯運(yùn)算符&&、加減乘除符號(hào)等。
3.5、采用 4 個(gè)空格縮進(jìn),禁止使用 tab 字符。
說明:如果使用 tab 縮進(jìn),必須設(shè)置 1 個(gè) tab 為 4 個(gè)空格。IDEA 設(shè)置 tab 為 4 個(gè)空格時(shí),請(qǐng)勿勾選 Use tab character;而在 eclipse 中,必須勾選 insert spaces for tabs。
正例: (涉及 1-5 點(diǎn))
public static void main(String args[]) {
// 縮進(jìn) 4個(gè)空格
String say = "hello";
// 運(yùn)算符的左右必須有一個(gè)空格
int flag = 0;
// 關(guān)鍵詞 if與括號(hào)之間必須有一個(gè)空格,括號(hào)內(nèi) f與左括號(hào),1與右括號(hào)不需要空格
if (flag == 0) {
System.out.println(say);
}
// 左大括號(hào)前加空格且不換行;左大括號(hào)后換行
if (flag == 1) {
System.out.println("world");
// 右大括號(hào)前換行,右大括號(hào)后有 else,不用換行
} else {
System.out.println("ok");
// 右大括號(hào)做為結(jié)束,必須換行
}
}
3.6、注釋的雙斜線與注釋內(nèi)容之間有且僅有一個(gè)空格。
正例:
// 這是示例注釋,請(qǐng)注意在雙斜線之后有一個(gè)空格
String param = new String();
3.7、在進(jìn)行類型強(qiáng)制轉(zhuǎn)換時(shí),右括號(hào)與強(qiáng)制轉(zhuǎn)換值之間不需要任何空格隔開。
正例:
first = 1000000000000L;
int second = (int)first + 2;
3.8、單行字符數(shù)限不超過 120 個(gè),超出需要換行時(shí) 遵循如下原則:
1) 第二行相對(duì)一縮進(jìn) 4個(gè)空格,從第三行開始不再繼續(xù)縮進(jìn)參考示例。
2) 運(yùn)算符與下文一起換行。
3) 方法調(diào)用的點(diǎn)符號(hào)與下文一起換行。
4) 在多個(gè)參數(shù)超長(zhǎng),逗號(hào)后進(jìn)行換行。
5) 在括號(hào)前不要換行,見反例。
正例:
StringBuffer sb = new StringBuffer();
//超過 120個(gè)字符的情況下,換行縮進(jìn) 4個(gè)空格,并且方法前的點(diǎn)符號(hào)一起換行
sb.append("zi").append("xin")…
.append("huang");</pre>
反例:
StringBuffer sb = new StringBuffer();
//超過120個(gè)字符的情況下,不要在括號(hào)前換行
sb.append("zi").append("xin")...append
("huang");
//參數(shù)很多的方法調(diào)用可能超過120個(gè)字符,不要在逗號(hào)前換行
method(args1, args2, args3, ...
, argsX);
3.9、方法參數(shù)在定義和傳入時(shí),多個(gè)參數(shù)逗號(hào)后邊必須加空格。
正例:下例中實(shí)參的 args1,后邊必須要有一個(gè)空格。
method(args1, args2, args3);
3.10、IDE 的 text file encoding 設(shè)置為 UTF-8; IDE 中文件的換行符使用 Unix 格式,不要使用 Windows 格式。
3.11、單個(gè)方法的總行數(shù)不超過 80 行。
說明:除注釋之外的方法簽名、左右大括號(hào)、方法內(nèi)代碼、空行、回車及任何不可見字符的總行數(shù)不超過 80 行。
正例:代碼邏輯分清紅花和綠葉,個(gè)性和共性,綠葉邏輯單獨(dú)出來(lái)成為額外方法,使主干代碼更加清晰;共性邏輯抽取成為共性方法,便于復(fù)用和維護(hù)。
3.12、沒有必要增加若干空格來(lái)使變量的賦值等號(hào)與上一行對(duì)應(yīng)位置的等號(hào)對(duì)齊。
正例:
int a = 3;
long b = 4L;
float c = 5F;
StringBuffer sb = new StringBuffer();
說明:增加 sb 這個(gè)變量,如果需要對(duì)齊,則給 one、two、three 都要增加幾個(gè)空格,在變量比較多的情況下,是非常累贅的事情。
3.13、不同邏輯、不同語(yǔ)義、不同業(yè)務(wù)的代碼之間插入一個(gè)空行分隔開來(lái)以提升可讀性。
說明:任何情形,沒有必要插入多個(gè)空行進(jìn)行隔開。
4、OOP 規(guī)約
4.1、避免通過一個(gè)類的對(duì)象引用訪問此類的靜態(tài)變量或靜態(tài)方法,無(wú)謂增加編譯器解析成本,直接用類名來(lái)訪問即可
4.2、所有的覆寫方法,必須加@Override 注解。
說明:getObject()與 get0bject()的問題。一個(gè)是字母的 O,一個(gè)是數(shù)字的 0,加@Override 可以準(zhǔn)確判斷是否覆蓋成功。另外,如果在抽象類中對(duì)方法簽名進(jìn)行修改,其實(shí)現(xiàn)類會(huì)馬上編譯報(bào)錯(cuò)。
4.3、相同參數(shù)類型,相同業(yè)務(wù)含義,才可以使用 Java 的可變參數(shù),避免使用 Object。
說明:可變參數(shù)必須放置在參數(shù)列表的最后。(提倡同學(xué)們盡量不用可變參數(shù)編程)
正例:
public List<User> listUsers(String type, Long... ids) {...}
4.4、外部正在調(diào)用或者二方庫(kù)依賴的接口,不允許修改方法簽名,避免對(duì)接口調(diào)用方產(chǎn)生影響。接口過時(shí)必須加@Deprecated 注解,并清晰地說明采用的新接口或者新服務(wù)是什么。
4.5、不能使用過時(shí)的類或方法。
說明:java.net.URLDecoder 中的方法 decode(String encodeStr) 這個(gè)方法已經(jīng)過時(shí),應(yīng)該使用雙參數(shù) decode(String source, String encode)。接口提供方既然明確是過時(shí)接口,那么有義務(wù)同時(shí)提供新的接口;作為調(diào)用方來(lái)說,有義務(wù)去考證過時(shí)方法的新實(shí)現(xiàn)是什么。
4.6、Object 的 equals 方法容易拋空指針異常,應(yīng)使用常量或確定有值的對(duì)象來(lái)調(diào)用 equals。
正例:“test”.equals(object);
反例:object.equals(“test”);
說明:推薦使用 java.util.Objects#equals(JDK7 引入的工具類)。
4.7、所有整型包裝類對(duì)象之間值的比較,全部使用 equals 方法比較。
說明:對(duì)于 Integer var = ? 在-128 至 127 范圍內(nèi)的賦值,Integer 對(duì)象是在 IntegerCache.cache 產(chǎn)生,會(huì)復(fù)用已有對(duì)象,這個(gè)區(qū)間內(nèi)的 Integer 值可以直接使用==進(jìn)行判斷,但是這個(gè)區(qū)間之外的所有數(shù)據(jù),都會(huì)在堆上產(chǎn)生,并不會(huì)復(fù)用已有對(duì)象,這是一個(gè)大坑,推薦使用 equals 方法進(jìn)行判斷。
4.8、浮點(diǎn)數(shù)之間的等值判斷,基本數(shù)據(jù)類型不能用==來(lái)比較,包裝數(shù)據(jù)類型不能用 equals 來(lái)判斷。
說明:浮點(diǎn)數(shù)采用“尾數(shù)+階碼”的編碼方式,類似于科學(xué)計(jì)數(shù)法的“有效數(shù)字+指數(shù)”的表示方式。二進(jìn)制無(wú)法精確表示大部分的十進(jìn)制小數(shù),具體原理參考《碼出高效》。
反例:
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
if (a == b) {
// 預(yù)期進(jìn)入此代碼快,執(zhí)行其它業(yè)務(wù)邏輯
// 但事實(shí)上 a==b 的結(jié)果為 false
}
Float x = Float.valueOf(a);
Float y = Float.valueOf(b);
if (x.equals(y)) {
// 預(yù)期進(jìn)入此代碼快,執(zhí)行其它業(yè)務(wù)邏輯
// 但事實(shí)上 equals 的結(jié)果為 false
}
正例:
指定一個(gè)誤差范圍,兩個(gè)浮點(diǎn)數(shù)的差值在此范圍之內(nèi),則認(rèn)為是相等的。
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");
}
使用 BigDecimal 來(lái)定義值,再進(jìn)行浮點(diǎn)數(shù)的運(yùn)算操作。
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(c);
if (x.equals(y)) {
System.out.println("true");
}
4.9、定義數(shù)據(jù)對(duì)象 DO 類時(shí),屬性類型要與數(shù)據(jù)庫(kù)字段類型相匹配。
正例:數(shù)據(jù)庫(kù)字段的 bigint 必須與類屬性的 Long 類型相對(duì)應(yīng)。
反例:某個(gè)案例的數(shù)據(jù)庫(kù)表 id 字段定義類型 bigint unsigned,實(shí)際類對(duì)象屬性為 Integer,隨著 id 越來(lái)越大,超過 Integer 的表示范圍而溢出成為負(fù)數(shù)。
4.10、為了防止精度損失,禁止使用構(gòu)造方法 BigDecimal(double)的方式把 double 值轉(zhuǎn)化為 BigDecimal 對(duì)象。
說明:BigDecimal(double)存在精度損失風(fēng)險(xiǎn),在精確計(jì)算或值比較的場(chǎng)景中可能會(huì)導(dǎo)致業(yè)務(wù)邏輯異常。
如:BigDecimal g = new BigDecimal(0.1f); 實(shí)際的存儲(chǔ)值為:0.10000000149
正例:優(yōu)先推薦入?yún)?String 的構(gòu)造方法,或使用 BigDecimal 的 valueOf 方法,此方法內(nèi)部其實(shí)執(zhí)行了 Double 的 toString,而 Double 的 toString 按 double 的實(shí)際能表達(dá)的精度對(duì)尾數(shù)進(jìn)行了截?cái)唷?/p>
BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = BigDecimal.valueOf(0.1);
關(guān)于基本數(shù)據(jù)類型與包裝數(shù)據(jù)類型的使用標(biāo)準(zhǔn)如下:
4.11、所有的 POJO 類屬性必須使用包裝數(shù)據(jù)類型。
4.12、RPC 方法的返回值和參數(shù)必須使用包裝數(shù)據(jù)類型。
4.13、所有的局部變量使用基本數(shù)據(jù)類型。
說明:POJO 類屬性沒有初值是提醒使用者在需要使用時(shí),必須自己顯式地進(jìn)行賦值,任何 NPE 問題,或者入庫(kù)檢查,都由使用者來(lái)保證。
正例:數(shù)據(jù)庫(kù)的查詢結(jié)果可能是 null,因?yàn)樽詣?dòng)拆箱,用基本數(shù)據(jù)類型接收有 NPE 風(fēng)險(xiǎn)。
反例:比如顯示成交總額漲跌情況,即正負(fù) x%,x 為基本數(shù)據(jù)類型,調(diào)用的 RPC 服務(wù),調(diào)用不成功時(shí),返回的是默認(rèn)值,頁(yè)面顯示為 0%,這是不合理的,應(yīng)該顯示成中劃線。所以包裝數(shù)據(jù)類型的 null 值,能夠表示額外的信息,如:遠(yuǎn)程調(diào)用失敗,異常退出。
4.14、定義 DO/DTO/VO 等 POJO 類時(shí),不要設(shè)定任何屬性默認(rèn)值。
反例:POJO 類的 createTime 默認(rèn)值為 new Date(),但是這個(gè)屬性在數(shù)據(jù)提取時(shí)并沒有置入具體值,在更新其它字段時(shí)又附帶更新了此字段,導(dǎo)致創(chuàng)建時(shí)間被修改成當(dāng)前時(shí)間。
4.15、序列化類新增屬性時(shí),請(qǐng)不要修改 serialVersionUID 字段,避免反序列失敗;如果完全不兼容升級(jí),避免反序列化混亂,那么請(qǐng)修改 serialVersionUID 值。
說明:注意 serialVersionUID 不一致會(huì)拋出序列化運(yùn)行時(shí)異常。
4.16、構(gòu)造方法里面禁止加入任何業(yè)務(wù)邏輯,如果有初始化邏輯,請(qǐng)放在 init 方法中。
4.17、POJO 類必須寫 toString 方法。使用 IDE 中的工具:source> generate toString 時(shí),如果繼承了另一個(gè) POJO 類,注意在前面加一下 super.toString。
說明:在方法執(zhí)行拋出異常時(shí),可以直接調(diào)用 POJO 的 toString()方法打印其屬性值,便于排查問題。
4.18、禁止在 POJO 類中,同時(shí)存在對(duì)應(yīng)屬性 xxx 的 isXxx()和 getXxx()方法。
說明:框架在調(diào)用屬性 xxx 的提取方法時(shí),并不能確定哪個(gè)方法一定是被優(yōu)先調(diào)用到。
4.19、使用索引訪問用 String 的 split 方法得到的數(shù)組時(shí),需做最后一個(gè)分隔符后有無(wú)內(nèi)容的檢查,否則會(huì)有拋 IndexOutOfBoundsException 的風(fēng)險(xiǎn)。
說明:
String str = "a,b,c,,"; String[] ary = str.split(",");
// 預(yù)期大于 3,結(jié)果是 3System.out.println(ary.length);
4.20、當(dāng)一個(gè)類有多個(gè)構(gòu)造方法,或者多個(gè)同名方法,這些方法應(yīng)該按順序放置在一起,便于閱讀,此條規(guī)則優(yōu)先于下一條。
4.21、類內(nèi)方法定義的順序依次是:公有方法或保護(hù)方法 > 私有方法 > getter / setter 方法。
說明:公有方法是類的調(diào)用者和維護(hù)者最關(guān)心的方法,首屏展示最好;保護(hù)方法雖然只是子類關(guān)心,也可能是“模板設(shè)計(jì)模式”下的核心方法;而私有方法外部一般不需要特別關(guān)心,是一個(gè)黑盒實(shí)現(xiàn);因?yàn)槌休d的信息價(jià)值較低,所有 Service 和 DAO 的 getter/setter 方法放在類體最后。
4.22、setter 方法中,參數(shù)名稱與類成員變量名稱一致,this.成員名 = 參數(shù)名。在 getter/setter 方法中,不要增加業(yè)務(wù)邏輯,增加排查問題的難度。
反例:
public Integer getData() {
if (condition) {
return this.data + 100;
} else {
return this.data - 100;
}
}
4.23、循環(huán)體內(nèi),字符串的連接方式,使用 StringBuilder 的 append 方法進(jìn)行擴(kuò)展。
說明:下例中,反編譯出的字節(jié)碼文件顯示每次循環(huán)都會(huì) new 出一個(gè) StringBuilder 對(duì)象,然后進(jìn)行 append 操作,最后通過 toString 方法返回 String 對(duì)象,造成內(nèi)存資源浪費(fèi)。
反例:
String str = "start";
for (
int i = 0; i < 100; i++) {
str = str + "hello";
}
4.24、final 可以聲明類、成員變量、方法、以及本地變量,下列情況使用 final 關(guān)鍵字:
不允許被繼承的類,如:String 類。
不允許修改引用的域?qū)ο蟆?br> 不允許被覆寫的方法,如:POJO 類的 setter 方法。
不允許運(yùn)行過程中重新賦值的局部變量。
避免上下文重復(fù)使用一個(gè)變量,使用 final 可以強(qiáng)制重新定義一個(gè)變量,方便更好地進(jìn)行重構(gòu)。
4.25、慎用 Object 的 clone 方法來(lái)拷貝對(duì)象。
說明:對(duì)象 clone 方法默認(rèn)是淺拷貝,若想實(shí)現(xiàn)深拷貝需覆寫 clone 方法實(shí)現(xiàn)域?qū)ο蟮纳疃缺闅v式拷貝。
4.25、類成員與方法訪問控制從嚴(yán):
如果不允許外部直接通過 new 來(lái)創(chuàng)建對(duì)象,那么構(gòu)造方法必須是 private。
工具類不允許有 public 或 default 構(gòu)造方法。
類非 static 成員變量并且與子類共享,必須是 protected。
類非 static 成員變量并且僅在本類使用,必須是 private。
類 static 成員變量如果僅在本類使用,必須是 private。
若是 static 成員變量,考慮是否為 final。
類成員方法只供類內(nèi)部調(diào)用,必須是 private。
類成員方法只對(duì)繼承類公開,那么限制為 protected。
說明:任何類、方法、參數(shù)、變量,嚴(yán)控訪問范圍。過于寬泛的訪問范圍,不利于模塊解耦。思考:如果是一個(gè) private 的方法,想刪除就刪除,可是一個(gè) public 的 service 成員方法或成員變量,刪除一下,不得手心冒點(diǎn)汗嗎?變量像自己的小孩,盡量在自己的視線內(nèi),變量作用域太大,無(wú)限制的到處跑,那么你會(huì)擔(dān)心的。
5 、集合處理
5.1、 hashCode 和 equals 的處理
遵循如下規(guī)則:
只要覆寫 equals,就必須覆寫 hashCode。
因?yàn)?Set 存儲(chǔ)的是不重復(fù)的對(duì)象,依據(jù) hashCode 和 equals 進(jìn)行判斷,所以 Set 存儲(chǔ)的對(duì)象必須覆 寫這兩個(gè)方法。
如果自定義對(duì)象作為 Map 的鍵,那么必須覆寫 hashCode 和 equals。
說明:String 已覆寫 hashCode 和 equals 方法,所以我們可以愉快地使用 String 對(duì)象作為 key 來(lái)使用。
5.2、ArrayList 的 subList 不可強(qiáng)轉(zhuǎn)成 ArrayList
否則會(huì)拋出 ClassCastException 異 常,即 java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。
說明:subList 返回的是 ArrayList 的內(nèi)部類 SubList,并不是 ArrayList 而是 ArrayList 的一個(gè)視圖,對(duì)于 SubList 子列表的所有操作最終會(huì)反映到原列表上。
5.3、Map 的方法 keySet()/values()/entrySet()返回集合對(duì)象時(shí)
使用 Map 的方法 keySet()/values()/entrySet()返回集合對(duì)象時(shí),不可以對(duì)其進(jìn)行添加元素操作,否則會(huì)拋出 UnsupportedOperationException 異常。
5.4、Collections 類返回的對(duì)象,如:emptyList()/singletonList()等都是 immutable list,不可對(duì)其進(jìn)行添加或者刪除元素的操作。
如果查詢無(wú)結(jié)果,返回 Collections.emptyList()空集合對(duì)象,調(diào)用方一旦進(jìn)行了添加元素的操作,就會(huì)觸發(fā) UnsupportedOperationException 異常。
5.5、在 subList 場(chǎng)景中,高度注意對(duì)原集合元素的增加或刪除,均會(huì)導(dǎo)致子列表的遍歷、增加、刪除產(chǎn)生 ConcurrentModificationException 異常。
5.6、使用集合轉(zhuǎn)數(shù)組的方法,必須使用集合的 toArray(T[] array),傳入的是類型完全一致、長(zhǎng)度為 0 的空數(shù)組。
反例:直接使用 toArray 無(wú)參方法存在問題,此方法返回值只能是 Object[]類,若強(qiáng)轉(zhuǎn)其它類型數(shù)組將出現(xiàn) ClassCastException 錯(cuò)誤。
正例:
List<String> list = new ArrayList<>(2);
list.add("guan");
list.add("bao");
String[] array = list.toArray(new String[0]);
說明:使用 toArray 帶參方法,數(shù)組空間大小的 length:
等于 0,動(dòng)態(tài)創(chuàng)建與 size 相同的數(shù)組,性能最好。
大于 0 但小于 size,重新創(chuàng)建大小等于 size 的數(shù)組,增加 GC 負(fù)擔(dān)。
等于 size,在高并發(fā)情況下,數(shù)組創(chuàng)建完成之后,size 正在變大的情況下,負(fù)面影響與上相同。
大于 size,空間浪費(fèi),且在 size 處插入 null 值,存在 NPE 隱患。
5.7、在使用 Collection 接口任何實(shí)現(xiàn)類的 addAll()方法時(shí),都要對(duì)輸入的集合參數(shù)進(jìn)行NPE 判斷。
說明:在 ArrayList#addAll 方法的第一行代碼即 Object[] a = c.toArray(); 其中 c 為輸入集合參數(shù),如果為 null,則直接拋出異常。
5.8、使用工具類 Arrays.asList()把數(shù)組轉(zhuǎn)換成集合時(shí),不能使用其修改集合相關(guān)的方法,它的 add/remove/clear 方法會(huì)拋出 UnsupportedOperationException 異常。
說明:asList 的返回對(duì)象是一個(gè) Arrays 內(nèi)部類,并沒有實(shí)現(xiàn)集合的修改方法。Arrays.asList 體現(xiàn)的是適配器模式,只是轉(zhuǎn)換接口,后臺(tái)的數(shù)據(jù)仍是數(shù)組。
String[] str = new String[] { "yang", "hao" };
List list = Arrays.asList(str);
第一種情況:list.add(“yangguanbao”); 運(yùn)行時(shí)異常。
第二種情況:str[0] = “changed”; 也會(huì)隨之修改,反之亦然。
5.9、泛型通配符<? extends T>來(lái)接收返回的數(shù)據(jù),此寫法的泛型集合不能使用 add 方 法,而<? super T>不能使用 get 方法,作為接口調(diào)用賦值時(shí)易出錯(cuò)。
說明:擴(kuò)展說一下 PECS(Producer Extends Consumer Super)原則:
第一、頻繁往外讀取內(nèi)容的,適合用<? extends T>。
第二、經(jīng)常往里插入的,適合用<? super T>
5.10、在無(wú)泛型限制定義的集合賦值給泛型限制的集合時(shí),在使用集合元素時(shí),需要進(jìn)行 instanceof 判斷,避免拋出 ClassCastException 異常。
說明:畢竟泛型是在 JDK5 后才出現(xiàn),考慮到向前兼容,編譯器是允許非泛型集合與泛型集合互相賦值。
反例:
List<String> generics = null;
List notGenerics = new ArrayList(10);
notGenerics.add(new Object());
notGenerics.add(new Integer(1));
generics = notGenerics;
// 此處拋出 ClassCastException 異常String string = generics.get(0);
5.11、不要在 foreach 循環(huán)里進(jìn)行元素的 remove/add 操作。remove 元素請(qǐng)使用 Iterator 方式,如果并發(fā)操作,需要對(duì) Iterator 對(duì)象加鎖。
正例:
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);
}
}
說明:以上代碼的執(zhí)行結(jié)果肯定會(huì)出乎大家的意料,那么試一下把“1”換成“2”,會(huì)是同樣的結(jié)果嗎?
5.12、在 JDK7 版本及以上,Comparator 實(shí)現(xiàn)類要滿足如下三個(gè)條件,不然 Arrays.sort,Collections.sort 會(huì)拋 IllegalArgumentException 異常。
說明:三個(gè)條件如下
x,y 的比較結(jié)果和 y,x 的比較結(jié)果相反。
x>y,y>z,則 x>z。
x=y,則 x,z 比較結(jié)果和 y,z 比較結(jié)果相同。
反例:下例中沒有處理相等的情況,交換兩個(gè)對(duì)象判斷結(jié)果并不互反,不符合第一個(gè)條件,在實(shí)際使用中可能會(huì)出現(xiàn)異常。
new Comparator<Student>() {
@Override public int compare(Student o1, Student o2) {
return o1.getId() > o2.getId() ? 1 : -1;
}
};
5.13、集合泛型定義時(shí),在 JDK7 及以上,使用 diamond 語(yǔ)法或全省略。
說明:菱形泛型,即 diamond,直接使用<>來(lái)指代前邊已經(jīng)指定的類型。
正例:
diamond 方式,即<>
HashMap<String, String> userCache = new HashMap<>(16);
// 全省略方式ArrayList<User> users = new ArrayList(10);
5.14、集合初始化時(shí),指定集合初始值大小。
說明:HashMap 使用 HashMap(int initialCapacity) 初始化。
正例:initialCapacity = (需要存儲(chǔ)的元素個(gè)數(shù) / 負(fù)載因子) + 1。注意負(fù)載因子(即 loader factor)默認(rèn)為 0.75,如果暫時(shí)無(wú)法確定初始值大小,請(qǐng)?jiān)O(shè)置為 16(即默認(rèn)值)。
反例:HashMap 需要放置 1024 個(gè)元素,由于沒有設(shè)置容量初始大小,隨著元素不斷增加,容量 7 次被迫擴(kuò)大,resize 需要重建 hash 表,嚴(yán)重影響性能。
5.15、使用 entrySet 遍歷 Map 類集合 KV,而不是 keySet 方式進(jìn)行遍歷。
說明:keySet 其實(shí)是遍歷了 2 次,一次是轉(zhuǎn)為 Iterator 對(duì)象,另一次是從 hashMap 中取出 key 所對(duì)應(yīng)的 value。而 entrySet 只是遍歷了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.forEach 方法。
正例:values()返回的是 V 值集合,是一個(gè) list 集合對(duì)象;keySet()返回的是 K 值集合,是一個(gè) Set 集合對(duì)象;entrySet()返回的是 K-V 值組合集合。
5.16、高度注意 Map 類集合 K/V 能不能存儲(chǔ) null 值的情況,如下表格:
集合類
Key
Value
Super
說明
Hashtable
不允許為 null
不允許為 null
Dictionary
線程安全
ConcurrentHashMap
不允許為 null
不允許為 null
AbstractMap
鎖分段技術(shù)(JDK8:CAS)
TreeMap
不允許為 null
允許為 null
AbstractMap
線程不安全
HashMap
允許為 null
允許為 null
AbstractMap
線程不安全
反例:由于 HashMap 的干擾,很多人認(rèn)為 ConcurrentHashMap 是可以置入 null 值,而事實(shí)上,存儲(chǔ) null 值時(shí)會(huì)拋出 NPE 異常。
5.17 合理利用好集合的有序性(sort)和穩(wěn)定性(order),避免集合的無(wú)序性(unsort)和不穩(wěn)定性(unorder)帶來(lái)的負(fù)面影響。
說明:有序性是指遍歷的結(jié)果是按某種比較規(guī)則依次排列的。穩(wěn)定性指集合每次遍歷的元素次序是一定的。如:ArrayList 是 order/unsort;HashMap 是 order/unsort;TreeSet 是 order/sort。
5.18 利用 Set 元素唯一的特性,可以快速對(duì)一個(gè)集合進(jìn)行去重操作,避免使用 List 的 contains 方法進(jìn)行遍歷、對(duì)比、去重操作。
6、 并發(fā)處理
6.1、獲取單例對(duì)象需要保證線程安全,其中的方法也要保證線程安全。
說明:資源驅(qū)動(dòng)類、工具類、單例工廠類都需要注意。
6.2、創(chuàng)建線程或線程池時(shí)請(qǐng)指定有意義的線程名稱,方便出錯(cuò)時(shí)回溯。
正例:自定義線程工廠,并且根據(jù)外部特征進(jìn)行分組,比如機(jī)房信息。
public class UserThreadFactory implements ThreadFactory {
private final String name Prefix;
private final AtomicInteger nextId = new AtomicInteger(1);
// 定義線程組名稱,在 jstack 問題排查時(shí),非常有幫助
UserThreadFactory(String whatFeaturOfGroup) {
namePrefix = "From UserThreadFactory's " + whatFeaturOfGroup + "-Worker-";
}
@Override
public Thread newThread(Runnable task) {
String name = namePrefix + nextId.getAndIncrement();
Thread thread = new Thread(null, task, name, 0, false);
System.out.println(thread.getName());
return thread;
}
}
6.3、線程資源必須通過線程池提供,不允許在應(yīng)用中自行顯式創(chuàng)建線程。
說明:線程池的好處是減少在創(chuàng)建和銷毀線程上所消耗的時(shí)間以及系統(tǒng)資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統(tǒng)創(chuàng)建大量同類線程而導(dǎo)致消耗完內(nèi)存或者“過度切換”的問題。
6.4、線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)。
說明:Executors 返回的線程池對(duì)象的弊端如下:
FixedThreadPool 和 SingleThreadPool:
允許的請(qǐng)求隊(duì)列長(zhǎng)度為 Integer.MAX_VALUE,可能會(huì)堆積大量的請(qǐng)求,從而導(dǎo)致 OOM。
CachedThreadPool:
允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE,可能會(huì)創(chuàng)建大量的線程,從而導(dǎo)致 OOM。
6.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 的應(yīng)用,可以使用 Instant 代替 Date,LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable thread-safe。
6.6、必須回收自定義的 ThreadLocal 變量,尤其在線程池場(chǎng)景下,線程經(jīng)常會(huì)被復(fù)用,如果不清理自定義的 ThreadLocal 變量,可能會(huì)影響后續(xù)業(yè)務(wù)邏輯和造成內(nèi)存泄露等問題。盡量在代理中使用 try-finally 塊進(jìn)行回收。
正例:
ThreadLocal.set(userInfo);
try {
// ...
} finally {
objectThreadLocal.remove();
}
6.7、高并發(fā)時(shí),同步調(diào)用應(yīng)該去考量鎖的性能損耗。能用無(wú)鎖數(shù)據(jù)結(jié)構(gòu),就不要用鎖;能鎖區(qū)塊,就不要鎖整個(gè)方法體;能用對(duì)象鎖,就不要用類鎖。
說明:盡可能使加鎖的代碼塊工作量盡可能的小,避免在鎖代碼塊中調(diào)用 RPC 方法。
6.8、對(duì)多個(gè)資源、數(shù)據(jù)庫(kù)表、對(duì)象同時(shí)加鎖時(shí),需要保持一致的加鎖順序,否則可能會(huì)造成死鎖。
說明:線程一需要對(duì)表 A、B、C 依次全部加鎖后才可以進(jìn)行更新操作,那么線程二的加鎖順序也必須是 A、B、C,否則可能出現(xiàn)死鎖。
6.9、在使用阻塞等待獲取鎖的方式中,必須在 try 代碼塊之外,并且在加鎖方法與 try 代碼塊之間沒有任何可能拋出異常的方法調(diào)用,避免加鎖成功后,在 finally 中無(wú)法解鎖。
說明一:如果在 lock 方法與 try 代碼塊之間的方法調(diào)用拋出異常,那么無(wú)法解鎖,造成其它線程無(wú)法成功獲取鎖。
說明二:如果 lock 方法在 try 代碼塊之內(nèi),可能由于其它方法拋出異常,導(dǎo)致在 finally 代碼塊中,unlock 對(duì)未加鎖的對(duì)象解鎖,它會(huì)調(diào)用 AQS 的 tryRelease 方法(取決于具體實(shí)現(xiàn)類),拋出 IllegalMonitorStateException 異常。
說明三:在 Lock 對(duì)象的 lock 方法實(shí)現(xiàn)中可能拋出 unchecked 異常,產(chǎn)生的后果與說明二相同。
正例:
Lock lock = new XxxLock();
// ...lock.lock();
try {
doSomething();
doOthers();
} finally {
lock.unlock();
}
反例:
Lock lock = new XxxLock();
// ...
try {
// 如果此處拋出異常,則直接執(zhí)行 finally 代碼塊
doSomething();
// 無(wú)論加鎖是否成功,
finally 代碼塊都會(huì)執(zhí)行
lock.lock();
doOthers();
} finally {
lock.unlock();
}
6.10、在使用嘗試機(jī)制來(lái)獲取鎖的方式中,進(jìn)入業(yè)務(wù)代碼塊之前,必須先判斷當(dāng)前線程是否持有鎖。鎖的釋放規(guī)則與鎖的阻塞等待方式相同。
說明:Lock 對(duì)象的 unlock 方法在執(zhí)行時(shí),它會(huì)調(diào)用 AQS 的 tryRelease 方法(取決于具體實(shí)現(xiàn)類),如果當(dāng)前線程不持有鎖,則拋出 IllegalMonitorStateException 異常。
正例:
Lock lock = new XxxLock();
// ...boolean isLocked = lock.tryLock();
if (isLocked) {
try {
doSomething();
doOthers();
} finally {
lock.unlock();
}
}
6.11 并發(fā)修改同一記錄時(shí),避免更新丟失,需要加鎖。要么在應(yīng)用層加鎖,要么在緩存加鎖,要么在數(shù)據(jù)庫(kù)層使用樂觀鎖,使用 version 作為更新依據(jù)。
說明:如果每次訪問沖突概率小于 20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數(shù)不得小于 3 次。
6.11、線程并行處理定時(shí)任務(wù)時(shí),Timer 運(yùn)行多個(gè) TimeTask 時(shí),只要其中之一沒有捕獲拋出的異常,其它任務(wù)便會(huì)自動(dòng)終止運(yùn)行,如果在處理定時(shí)任務(wù)時(shí)使用 ScheduledExecutorService 則沒有這個(gè)問題。
6.12、資金相關(guān)的金融敏感信息,使用悲觀鎖策略。
說明:樂觀鎖在獲得鎖的同時(shí)已經(jīng)完成了更新操作,校驗(yàn)邏輯容易出現(xiàn)漏洞,另外,樂觀鎖對(duì)沖突的解決策略有較復(fù)雜的要求,處理不當(dāng)容易造成系統(tǒng)壓力或數(shù)據(jù)異常,所以資金相關(guān)的金融敏感信息不建議使用樂觀鎖更新。
6.13、使用 CountDownLatch 進(jìn)行異步轉(zhuǎn)同步操作,每個(gè)線程退出前必須調(diào)用 countDown 方法,線程執(zhí)行代注意 catch 異常,確保 countDown 方法被執(zhí)行到,避免主線程無(wú)法執(zhí)行至 await 方法,直到超時(shí)才返回結(jié)果。
說明:注意,子線程拋出異常堆棧,不能在主線程 try-catch 到。
6.14、Random 實(shí)例被多線程使用,雖然共享該實(shí)例是線程安全的,但會(huì)因競(jìng)爭(zhēng)同一 seed 導(dǎo)致的性能下降。
說明:Random 實(shí)例包括 java.util.Random 的實(shí)例或者 Math.random()的方式。
正例:在 JDK7 之后,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前,需要編碼保證每個(gè)線程持有一個(gè)實(shí)例。
6.15、在并發(fā)場(chǎng)景下,通過雙重檢查鎖(double-checked locking)實(shí)現(xiàn)延遲初始化的優(yōu)化問題隱患(可參考 The “Double-Checked Locking is Broken” Declaration),推薦解決方案中較為簡(jiǎn)單一種(適用于 JDK5 及以上版本),將目標(biāo)屬性聲明為 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...
}
6.16、volatile 解決多線程內(nèi)存不可見問題。對(duì)于一寫多讀,是可以解決變量同步問題,但
是如果多寫,同樣無(wú)法解決線程安全問題。
說明:如果是 count++操作,使用如下類實(shí)現(xiàn):
AtomicInteger count = new AtomicInteger();
count.addAndGet(1);
如果是 JDK8,推薦使用 LongAdder 對(duì)象,比 AtomicLong 性能更好(減少樂觀
鎖的重試次數(shù))。
6.17、HashMap 在容量不夠進(jìn)行 resize 時(shí)由于高并發(fā)可能出現(xiàn)死鏈,導(dǎo)致 CPU 飆升,在
開發(fā)過程中可以使用其它數(shù)據(jù)結(jié)構(gòu)或加鎖來(lái)規(guī)避此風(fēng)險(xiǎn)。
6.17、ThreadLocal 對(duì)象使用 static 修飾,ThreadLocal 無(wú)法解決共享對(duì)象的更新問題。
說明:這個(gè)變量是針對(duì)一個(gè)線程內(nèi)所有操作共享的,所以設(shè)置為靜態(tài)變量,所有此類實(shí)例共享此靜態(tài)變
量,也就是說在類第一次被使用時(shí)裝載,只分配一塊存儲(chǔ)空間,所有此類的對(duì)象(只要是這個(gè)線程內(nèi)定義
的)都可以操控這個(gè)變量。
7、 控制語(yǔ)句
7.1、在一個(gè) switch 塊內(nèi),每個(gè) case 要么通過 continue/break/return 等來(lái)終止,要么注釋說明程序?qū)⒗^續(xù)執(zhí)行到哪一個(gè) case 為止;在一個(gè) switch 塊內(nèi),都必須包含一個(gè) default 語(yǔ)句并且放在最后,即使它什么代碼也沒有。
說明:注意 break 是退出 switch 語(yǔ)句塊,而 return 是退出方法體。
7.2、當(dāng) switch 括號(hào)內(nèi)的變量類型為 String 并且此變量為外部參數(shù)時(shí),必須先進(jìn)行 null 判斷。
反例:猜猜下面的代碼輸出是什么?
public class SwitchString {
public static void main(String[] args) {
method(null);
}
public static void method(String param) {
switch (param) {
// 肯定不是進(jìn)入這里
case "sth": System.out.println("it's sth");
break;
// 也不是進(jìn)入這里
case "null": System.out.println("it's null");
break;
// 也不是進(jìn)入這里
default:
System.out.println("default");
}
}
}
7.3、在 if/else/for/while/do 語(yǔ)句中必須使用大括號(hào)。
說明:即使只有一行代碼,避免采用單行的編碼方式:if (condition) statements;
7.4、在高并發(fā)場(chǎng)景中,避免使用”等于”判斷作為中斷或退出的條件。
說明:如果并發(fā)控制沒有處理好,容易產(chǎn)生等值判斷被“擊穿”的情況,使用大于或小于的區(qū)間判斷條件來(lái)代替。
反例:判斷剩余獎(jiǎng)品數(shù)量等于 0 時(shí),終止發(fā)放獎(jiǎng)品,但因?yàn)椴l(fā)處理錯(cuò)誤導(dǎo)致獎(jiǎng)品數(shù)量瞬間變成了負(fù)數(shù),這樣的話,活動(dòng)無(wú)法終止。
7.5、表達(dá)異常的分支時(shí),少用 if-else 方式,這種方式可以改寫成:
if (condition) {
... return obj;
}
// 接著寫 else 的業(yè)務(wù)邏輯代碼;
說明:如果非使用 if()…else if()…else…方式表達(dá)邏輯,避免后續(xù)代碼維護(hù)困難,請(qǐng)勿超過 3 層。
正例:超過 3 層的 if-else 的邏輯判斷代碼可以使用衛(wèi)語(yǔ)句、策略模式、狀態(tài)模式等來(lái)實(shí)現(xiàn),其中衛(wèi)語(yǔ)句即代碼邏輯先考慮失敗、異常、中斷、退出等直接返回的情況,以方法多個(gè)出口的方式,解決代碼中判斷分支嵌套的問題,這是逆向思維的體現(xiàn)。
示例如下:
public void findBoyfriend(Man man) {
if (man.isUgly()) {
System.out.println("本姑娘是外貌協(xié)會(huì)的資深會(huì)員");
return;
}
if (man.isPoor()) {
System.out.println("貧賤夫妻百事哀");
return;
}
if (man.isBadTemper()) {
System.out.println("銀河有多遠(yuǎn),你就給我滾多遠(yuǎn)");
return;
}
System.out.println("可以先交往一段時(shí)間看看");
}
7.6、除常用方法(如 getXxx/isXxx)等外,不要在條件判斷中執(zhí)行其它復(fù)雜的語(yǔ)句,將復(fù)雜邏輯判斷的結(jié)果賦值給一個(gè)有意義的布爾變量名,以提高可讀性。
說明:很多 if 語(yǔ)句內(nèi)的邏輯表達(dá)式相當(dāng)復(fù)雜,與、或、取反混合運(yùn)算,甚至各種方法縱深調(diào)用,理解成本非常高。如果賦值一個(gè)非常好理解的布爾變量名字,則是件令人爽心悅目的事情。
正例:
// 偽代碼如下
final boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed) {
...
}
反例:
public final void acquire(long arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
selfInterrupt();
}
}
7.7、不要在其它表達(dá)式(尤其是條件表達(dá)式)中,插入賦值語(yǔ)句。
說明:賦值點(diǎn)類似于人體的穴位,對(duì)于代碼的理解至關(guān)重要,所以賦值語(yǔ)句需要清晰地單獨(dú)成為一行。
反例:
public Lock getLock(boolean fair) {
// 算術(shù)表達(dá)式中出現(xiàn)賦值操作,容易忽略 count 值已經(jīng)被改變
threshold = (count = Integer.MAX_VALUE) - 1;
// 條件表達(dá)式中出現(xiàn)賦值操作,容易誤認(rèn)為是
sync==fair
return (sync = fair) ? new FairSync() : new NonfairSync();
}
7.8、循環(huán)體中的語(yǔ)句要考量性能,以下操作盡量移至循環(huán)體外處理,如定義對(duì)象、變量、獲取數(shù)據(jù)庫(kù)連接,進(jìn)行不必要的 try-catch 操作(這個(gè) try-catch 是否可以移至循環(huán)體外)
7.9、避免采用取反邏輯運(yùn)算符。
說明:取反邏輯不利于快速理解,并且取反邏輯寫法必然存在對(duì)應(yīng)的正向邏輯寫法。
正例:使用 if (x < 628) 來(lái)表達(dá) x 小于 628。
反例:使用 if (!(x >= 628)) 來(lái)表達(dá) x 小于 628。
7.10、接口入?yún)⒈Wo(hù),這種場(chǎng)景常見的是用作批量操作的接口。
下列情形,需要進(jìn)行參數(shù)校驗(yàn):
調(diào)用頻次低的方法。
執(zhí)行時(shí)間開銷很大的方法。此情形中,參數(shù)校驗(yàn)時(shí)間幾乎可以忽略不計(jì),但如果因?yàn)閰?shù)錯(cuò)誤導(dǎo)致中間執(zhí)行回退,或者錯(cuò)誤,那得不償失。
需要極高穩(wěn)定性和可用性的方法。
對(duì)外提供的開放接口,不管是 RPC/API/HTTP 接口。
敏感權(quán)限入口。
7.11、下列情形,不需要進(jìn)行參數(shù)校驗(yàn)
極有可能被循環(huán)調(diào)用的方法。但在方法說明里必須注明外部參數(shù)檢查要求。
底層調(diào)用頻度比較高的方法。畢竟是像純凈水過濾的最后一道,參數(shù)錯(cuò)誤不太可能到底層才會(huì)暴露問題。一般 DAO 層與 Service 層都在同一個(gè)應(yīng)用中,部署在同一臺(tái)服務(wù)器中,所以 DAO 的參數(shù)校驗(yàn),可以省略。
被聲明成 private 只會(huì)被自己代碼所調(diào)用的方法,如果能夠確定調(diào)用方法的代碼傳入?yún)?shù)已經(jīng)做過檢查或者肯定不會(huì)有問題,此時(shí)可以不校驗(yàn)參數(shù)。
8、注釋規(guī)約
8.1、類、類屬性、類方法的注釋必須使用 Javadoc 規(guī)范,使用/*內(nèi)容/格式,不得使用// xxx 方式。
說明:在 IDE 編輯窗口中,Javadoc 方式會(huì)提示相關(guān)注釋,生成 Javadoc 可以正確輸出相應(yīng)注釋;在 IDE 中,工程調(diào)用方法時(shí),不進(jìn)入方法即可懸浮提示方法、參數(shù)、返回值的意義,提高閱讀效率。
8.2、所有的抽象方法(包括接口中的方法)必須要用 Javadoc 注釋、除了返回值、參數(shù)、異常說明外,還必須指出該方法做什么事情,實(shí)現(xiàn)什么功能。
說明:對(duì)子類的實(shí)現(xiàn)要求,或者調(diào)用注意事項(xiàng),請(qǐng)一并說明。
8.3、所有的類都必須添加創(chuàng)建者和創(chuàng)建日期
8.4、方法內(nèi)部單行注釋,在被注釋語(yǔ)句上方另起一行,使用//注釋。方法內(nèi)部多行注釋使用/* */注釋,注意與代碼對(duì)齊
8.5、所有的枚舉類型字段必須要有注釋,說明每個(gè)數(shù)據(jù)項(xiàng)的用途
8.6、與其“半吊子”英文來(lái)注釋,不如用中文注釋把問題說清楚。專有名詞與關(guān)鍵字保持英文原文即可。
反例:“TCP 連接超時(shí)”解釋成“傳輸控制協(xié)議連接超時(shí)”,理解反而費(fèi)腦筋。
8.7、代碼修改的同時(shí),注釋也要進(jìn)行相應(yīng)的修改,尤其是參數(shù)、返回值、異常、核心邏輯等的修改。
說明:代碼與注釋更新不同步,就像路網(wǎng)與導(dǎo)航軟件更新不同步一樣,如果導(dǎo)航軟件嚴(yán)重滯后,就失去了導(dǎo)航的意義。
8.8、謹(jǐn)慎注釋掉代碼。在上方詳細(xì)說明,而不是簡(jiǎn)單地注釋掉。如果無(wú)用,則刪除。
說明:代碼被注釋掉有兩種可能性:
后續(xù)會(huì)恢復(fù)此段代碼邏輯。
永久不用
前者如果沒有備注信息,難以知曉注釋動(dòng)機(jī)。后者建議直接刪掉(代碼倉(cāng)庫(kù)已然保存了歷史代碼)。
8.9、對(duì)于注釋的要求
第一、能夠準(zhǔn)確反映設(shè)計(jì)思想和代碼邏輯;
第二、能夠描述業(yè)務(wù)含義,使別的程序員能夠迅速了解到代碼背后的信息。完全沒有注釋的大段代碼對(duì)于閱讀者形同天書,注釋是給自己看的,即使隔很長(zhǎng)時(shí)間,也能清晰理解當(dāng)時(shí)的思路;注釋也是給繼任者看的,使其能夠快速接替自己的工作。
8.10、好的命名、代碼結(jié)構(gòu)是自解釋的,注釋力求精簡(jiǎn)準(zhǔn)確、表達(dá)到位。避免出現(xiàn)注釋的一個(gè)極端:過多過濫的注釋,代碼的邏輯一旦修改,修改注釋是相當(dāng)大的負(fù)擔(dān)。
反例:
put elephant into fridge put(elephant, fridge);
方法名 put,加上兩個(gè)有意義的變量名 elephant 和 fridge,已經(jīng)說明了這是在干什么,語(yǔ)義清晰的代碼不需要額外的注釋。
8.11、特殊注釋標(biāo)記,請(qǐng)注明標(biāo)記人與標(biāo)記時(shí)間。注意及時(shí)處理這些標(biāo)記,通過標(biāo)記掃描,經(jīng)常清理此類標(biāo)記。線上故障有時(shí)候就是來(lái)源于這些標(biāo)記處的代碼。
待辦事宜(TODO):(標(biāo)記人,標(biāo)記時(shí)間,[預(yù)計(jì)處理時(shí)間])
表示需要實(shí)現(xiàn),但目前還未實(shí)現(xiàn)的功能。這實(shí)際上是一個(gè) Javadoc 的標(biāo)簽,目前的 Javadoc 還沒有實(shí)現(xiàn),但已經(jīng)被廣泛使用。只能應(yīng)用于類,接口和方法(因?yàn)樗且粋€(gè) Javadoc 標(biāo)簽)。
錯(cuò)誤,不能工作(FIXME):(標(biāo)記人,標(biāo)記時(shí)間,[預(yù)計(jì)處理時(shí)間])
在注釋中用 FIXME 標(biāo)記某代碼是錯(cuò)誤的,而且不能工作,需要及時(shí)糾正的情況。
9、 其它
1)在使用正則表達(dá)式時(shí),利用好其預(yù)編譯功能,可以有效加快正則匹配速度。
說明:不要在方法體內(nèi)定義:Pattern pattern = Pattern.compile(“規(guī)則”);
2)【強(qiáng)制】velocity 調(diào)用 POJO 類的屬性時(shí),直接使用屬性名取值即可,模板引擎會(huì)自動(dòng)按規(guī)范調(diào)用 POJO 的 getXxx(),如果是 boolean 基本數(shù)據(jù)類型變量(boolean 命名不需要加 is 前 綴),會(huì)自動(dòng)調(diào)用 isXxx()方法。
說明:注意如果是 Boolean 包裝類對(duì)象,優(yōu)先調(diào)用 getXxx()的方法。
3)【強(qiáng)制】后臺(tái)輸送給頁(yè)面的變量必須加$!{var}——中間的感嘆號(hào)。
說明:如果 var 等于 null 或者不存在,那么${var}會(huì)直接顯示在頁(yè)面上。
4)【強(qiáng)制】注意 Math.random() 這個(gè)方法返回是 double 類型,注意取值的范圍 0≤x<1(能夠取到零值,注意除零異常),如果想獲取整數(shù)類型的隨機(jī)數(shù),不要將 x 放大 10 的若干倍然后取整,直接使用 Random 對(duì)象的 nextInt 或者 nextLong 方法。
5)【強(qiáng)制】獲取當(dāng)前毫秒數(shù) System.currentTimeMillis(); 而不是 new Date().getTime();
說明:如果想獲取更加精確的納秒級(jí)時(shí)間值,使用 System.nanoTime()的方式。在 JDK8 中,針對(duì)統(tǒng)計(jì)時(shí)間等場(chǎng)景,推薦使用 Instant 類。
6)【強(qiáng)制】日期格式化時(shí),傳入 pattern 中表示年份統(tǒng)一使用小寫的 y。
說明:日期格式化時(shí),yyyy 表示當(dāng)天所在的年,而大寫的 YYYY 代表是 week in which year(JDK7 之后引入的概念),意思是當(dāng)天所在的周屬于的年份,一周從周日開始,周六結(jié)束,只要本周跨年,返回的 YYYY 就是下一年。另外需要注意:
表示月份是大寫的 M
表示分鐘則是小寫的 m
24 小時(shí)制的是大寫的 H
12 小時(shí)制的則是小寫的 h
正例:表示日期和時(shí)間的格式如下所示:
new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
7)【推薦】不要在視圖模板中加入任何復(fù)雜的邏輯。
說明:根據(jù) MVC 理論,視圖的職責(zé)是展示,不要搶模型和控制器的活。
8)【推薦】任何數(shù)據(jù)結(jié)構(gòu)的構(gòu)造或初始化,都應(yīng)指定大小,避免數(shù)據(jù)結(jié)構(gòu)無(wú)限增長(zhǎng)吃光內(nèi)存。
9)【推薦】及時(shí)清理不再使用的代碼段或配置信息。
說明:對(duì)于垃圾代碼或過時(shí)配置,堅(jiān)決清理干凈,避免程序過度臃腫,代碼冗余。
正例:對(duì)于暫時(shí)被注釋掉,后續(xù)可能恢復(fù)使用的代碼片斷,在注釋代碼上方,統(tǒng)一規(guī)定使用三個(gè)斜杠(///)來(lái)說明注釋掉代碼的理由。
10、異常日志
1) 異常處理
【強(qiáng)制】Java 類庫(kù)中定義的可以通過預(yù)檢查方式規(guī)避的 RuntimeException 異常不應(yīng)該通過 catch 的方式來(lái)處理,比如:NullPointerException,IndexOutOfBoundsException 等等。
說明:無(wú)法通過預(yù)檢查的異常除外,比如,在解析字符串形式的數(shù)字時(shí),可能存在數(shù)字格式錯(cuò)誤,不得不通過 catch NumberFormatException 來(lái)實(shí)現(xiàn)。
正例:if (obj != null) {…}
反例:try { obj.method(); } catch (NullPointerException e) {…}
2)【強(qiáng)制】異常不要用來(lái)做流程控制,條件控制。
說明:異常設(shè)計(jì)的初衷是解決程序運(yùn)行中的各種意外情況,且異常的處理效率比條件判斷方式要低很多。
3)【強(qiáng)制】catch 時(shí)請(qǐng)分清穩(wěn)定代碼和非穩(wěn)定代碼,穩(wěn)定代碼指的是無(wú)論如何不會(huì)出錯(cuò)的代碼。
對(duì)于非穩(wěn)定代碼的 catch 盡可能進(jìn)行區(qū)分異常類型,再做對(duì)應(yīng)的異常處理。
說明:對(duì)大段代碼進(jìn)行 try-catch,使程序無(wú)法根據(jù)不同的異常做出正確的應(yīng)激反應(yīng),也不利于定位問題,這是一種不負(fù)責(zé)任的表現(xiàn)。
正例:用戶注冊(cè)的場(chǎng)景中,如果用戶輸入非法字符,或用戶名稱已存在,或用戶輸入密碼過于簡(jiǎn)單,在程序上作出分門別類的判斷,并提示給用戶。
4)【強(qiáng)制】捕獲異常是為了處理它,不要捕獲了卻什么都不處理而拋棄之,如果不想處理它,請(qǐng)將該異常拋給它的調(diào)用者。最外層的業(yè)務(wù)使用者,必須處理異常,將其轉(zhuǎn)化為用戶可以理解的內(nèi)容。
5)【強(qiáng)制】有 try 塊放到了事務(wù)代碼中,catch 異常后,如果需要回滾事務(wù),一定要注意手動(dòng)回滾事務(wù)。
6)【強(qiáng)制】finally 塊必須對(duì)資源對(duì)象、流對(duì)象進(jìn)行關(guān)閉,有異常也要做 try-catch。
說明:如果 JDK7 及以上,可以使用 try-with-resources 方式。
7)【強(qiáng)制】不要在 finally 塊中使用 return。
說明:try 塊中的 return 語(yǔ)句執(zhí)行成功后,并不馬上返回,而是繼續(xù)執(zhí)行 finally 塊中的語(yǔ)句,如果此處存在 return 語(yǔ)句,則在此直接返回,無(wú)情丟棄掉 try 塊中的返回點(diǎn)。
反例:
private int x = 0;
public int checkReturn() {
try {
// x 等于 1,此處不返回
return ++x;
} finally {
// 返回的結(jié)果是 2
return ++x;
}
}
8)【強(qiáng)制】捕獲異常與拋異常,必須是完全匹配,或者捕獲異常是拋異常的父類。
說明:如果預(yù)期對(duì)方拋的是繡球,實(shí)際接到的是鉛球,就會(huì)產(chǎn)生意外情況。
9)【強(qiáng)制】在調(diào)用 RPC、二方包、或動(dòng)態(tài)生成類的相關(guān)方法時(shí),捕捉異常必須使用 Throwable 類來(lái)進(jìn)行攔截。
說明:通過反射機(jī)制來(lái)調(diào)用方法,如果找不到方法,拋出 NoSuchMethodException。什么情況會(huì)拋出 NoSuchMethodError 呢?二方包在類沖突時(shí),仲裁機(jī)制可能導(dǎo)致引入非預(yù)期的版本使類的方法簽名不匹配,或者在字節(jié)碼修改框架(比如:ASM)動(dòng)態(tài)創(chuàng)建或修改類時(shí),修改了相應(yīng)的方法簽名。這些情況,即使代碼編譯期是正確的,但在代碼運(yùn)行期時(shí),會(huì)拋出 NoSuchMethodError。
10)【推薦】方法的返回值可以為 null,不強(qiáng)制返回空集合,或者空對(duì)象等,必須添加注釋充分說明什么情況下會(huì)返回 null 值。
說明:本手冊(cè)明確防止 NPE 是調(diào)用者的責(zé)任。即使被調(diào)用方法返回空集合或者空對(duì)象,對(duì)調(diào)用者來(lái)說,也并非高枕無(wú)憂,必須考慮到遠(yuǎn)程調(diào)用失敗、序列化失敗、運(yùn)行時(shí)異常等場(chǎng)景返回 null 的情況。
10.11、防止 NPE,是程序員的基本修養(yǎng),注意 NPE 產(chǎn)生的場(chǎng)景:
返回類型為基本數(shù)據(jù)類型,return 包裝數(shù)據(jù)類型的對(duì)象時(shí),自動(dòng)拆箱有可能產(chǎn)生 NPE。
反例:
public int f() {
return Integer 對(duì)象
}
如果為 null,自動(dòng)解箱拋 NPE。
數(shù)據(jù)庫(kù)的查詢結(jié)果可能為 null。
集合里的元素即使 isNotEmpty,取出的數(shù)據(jù)元素也可能為 null。
遠(yuǎn)程調(diào)用返回對(duì)象時(shí),一律要求進(jìn)行空指針判斷,防止 NPE。
對(duì)于 Session 中獲取的數(shù)據(jù),建議進(jìn)行 NPE 檢查,避免空指針。
級(jí)聯(lián)調(diào)用 obj.getA().getB().getC();一連串調(diào)用,易產(chǎn)生 NPE。
正例:使用 JDK8 的 Optional 類來(lái)防止 NPE 問題。
10.12、定義時(shí)區(qū)分 unchecked / checked 異常,避免直接拋出 new RuntimeException(),更不允許拋出 Exception 或者 Throwable,應(yīng)使用有業(yè)務(wù)含義的自定義異常。推薦業(yè)界已定義過的自定義異常,如:DAOException / ServiceException 等。
10.13、對(duì)于公司外的 http/api 開放接口必須使用“錯(cuò)誤碼”;而應(yīng)用內(nèi)部推薦異常拋出;跨應(yīng)用間 RPC 調(diào)用優(yōu)先考慮使用 Result 方式,封裝 isSuccess()方法、“錯(cuò)誤碼”、“錯(cuò)誤簡(jiǎn)短信息”。
說明:關(guān)于 RPC 方法返回方式使用 Result 方式的理由:
使用拋異常返回方式,調(diào)用方如果沒有捕獲到就會(huì)產(chǎn)生運(yùn)行時(shí)錯(cuò)誤。
如果不加棧信息,只是 new 自定義異常,加入自己的理解的 error message,對(duì)于調(diào)用端解決問題的幫助不會(huì)太多。如果加了棧信息,在頻繁調(diào)用出錯(cuò)的情況下,數(shù)據(jù)序列化和傳輸?shù)男阅軗p耗也是問題。
10.14、避免出現(xiàn)重復(fù)的代碼(Don’t Repeat Yourself),即 DRY 原則。
說明:隨意復(fù)制和粘貼代碼,必然會(huì)導(dǎo)致代碼的重復(fù),在以后需要修改時(shí),需要修改所有的副本,容易遺漏。必要時(shí)抽取共性方法,或者抽象公共類,甚至是組件化。
正例:一個(gè)類中有多個(gè) public 方法,都需要進(jìn)行數(shù)行相同的參數(shù)校驗(yàn)操作,這個(gè)時(shí)候請(qǐng)抽?。?/p>
private boolean checkParam(DTO dto) {...}
11、 日志規(guī)約
11.1、應(yīng)用中不可直接使用日志系統(tǒng)(Log4j、Logback)中的 API,而應(yīng)依賴使用日志框架 SLF4J 中的 API,使用門面模式的日志框架,有利于維護(hù)和各個(gè)類的日志處理方式統(tǒng)一。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Test.class);
11.2、所有日志文件至少保存 15 天,因?yàn)橛行┊惓>邆湟浴爸堋睘轭l次發(fā)生的特點(diǎn)。網(wǎng)絡(luò)運(yùn)行狀態(tài)、安全相關(guān)信息、系統(tǒng)監(jiān)測(cè)、管理后臺(tái)操作、用戶敏感操作需要留存相關(guān)的網(wǎng)絡(luò)日志不少于 6 個(gè)月。
11.3、應(yīng)用中的擴(kuò)展日志(如打點(diǎn)、臨時(shí)監(jiān)控、訪問日志等)命名方式:
appName_logType_logName.log。logType:日志類型,如 stats/monitor/access 等;logName:日志描述。這種命名的好處:通過文件名就可知道日志文件屬于什么應(yīng)用,什么類型,什么目的,也有利于歸類查找。
說明:推薦對(duì)日志進(jìn)行分類,如將錯(cuò)誤日志和業(yè)務(wù)日志分開存放,便于開發(fā)人員查看,也便于通過日志對(duì)系統(tǒng)進(jìn)行及時(shí)監(jiān)控。
正例:force-web 應(yīng)用中單獨(dú)監(jiān)控時(shí)區(qū)轉(zhuǎn)換異常,如:force_web_timeZoneConvert.log
11.4、在日志輸出時(shí),字符串變量之間的拼接使用占位符的方式。
說明:因?yàn)?String 字符串的拼接會(huì)使用 StringBuilder 的 append()方式,有一定的性能損耗。使用占位符僅是替換動(dòng)作,可以有效提升性能。
正例:logger.debug(“Processing trade with id: {} and symbol: {}”, id, symbol);
11.5、對(duì)于 trace/debug/info 級(jí)別的日志輸出,必須進(jìn)行日志級(jí)別的開關(guān)判斷。
說明:雖然在 debug(參數(shù))的方法體內(nèi)第一行代碼 isDisabled(Level.DEBUG_INT)為真時(shí)(Slf4j 的常見實(shí)現(xiàn) Log4j 和 Logback),就直接 return,但是參數(shù)可能會(huì)進(jìn)行字符串拼接運(yùn)算。此外,如果 debug(getName())這種參數(shù)內(nèi)有 getName()方法調(diào)用,無(wú)謂浪費(fèi)方法調(diào)用的開銷。
正例:
如果判斷為真,那么可以輸出 trace 和 debug 級(jí)別的日志if (logger.isDebugEnabled()) { logger.debug(“Current ID is: {} and name is: {}”, id, getName());}
11.6、避免重復(fù)打印日志,浪費(fèi)磁盤空間,務(wù)必在 log4j.xml 中設(shè)置 additivity=false。
正例:
11.7、異常信息應(yīng)該包括兩類信息:案發(fā)現(xiàn)場(chǎng)信息和異常堆棧信息。如果不處理,那么通過關(guān)鍵字 throws 往上拋出。
正例:logger.error(各類參數(shù)或者對(duì)象 toString() + “_” + e.getMessage(), e);
11.8、謹(jǐn)慎地記錄日志。生產(chǎn)環(huán)境禁止輸出 debug 日志;有選擇地輸出 info 日志;如果使用 warn 來(lái)記錄剛上線時(shí)的業(yè)務(wù)行為信息,一定要注意日志輸出量的問題,避免把服務(wù)器磁盤撐爆,并記得及時(shí)刪除這些觀察日志。
說明:大量地輸出無(wú)效日志,不利于系統(tǒng)性能提升,也不利于快速定位錯(cuò)誤點(diǎn)。記錄日志時(shí)請(qǐng)思考:這些日志真的有人看嗎?看到這條日志你能做什么?能不能給問題排查帶來(lái)好處?
11.9、可以使用 warn 日志級(jí)別來(lái)記錄用戶輸入?yún)?shù)錯(cuò)誤的情況,避免用戶投訴時(shí),無(wú)所適從。如非必要,請(qǐng)不要在此場(chǎng)景打出 error 級(jí)別,避免頻繁報(bào)警。
說明:注意日志輸出的級(jí)別,error 級(jí)別只記錄系統(tǒng)邏輯出錯(cuò)、異?;蛘咧匾腻e(cuò)誤信息。
11.10、盡量用英文來(lái)描述日志錯(cuò)誤信息,如果日志中的錯(cuò)誤信息用英文描述不清楚的話使用中文描述即可,否則容易產(chǎn)生歧義。
國(guó)際化團(tuán)隊(duì)或海外部署的服務(wù)器由于字符集問題,使用全英文來(lái)注釋和描述日志錯(cuò)誤信息。
12、單元測(cè)試
1)【強(qiáng)制】好的單元測(cè)試必須遵守 AIR 原則。
說明:?jiǎn)卧獪y(cè)試在線上運(yùn)行時(shí),感覺像空氣(AIR)一樣并不存在,但在測(cè)試質(zhì)量的保障上,卻是非常關(guān)鍵的。好的單元測(cè)試宏觀上來(lái)說,具有自動(dòng)化、獨(dú)立性、可重復(fù)執(zhí)行的特點(diǎn)。
A:Automatic(自動(dòng)化)
I:Independent(獨(dú)立性)
R:Repeatable(可重復(fù))
2)【強(qiáng)制】單元測(cè)試應(yīng)該是全自動(dòng)執(zhí)行的,并且非交互式的。測(cè)試用例通常是被定期執(zhí)行的,執(zhí)行過程必須完全自動(dòng)化才有意義。輸出結(jié)果需要人工檢查的測(cè)試不是一個(gè)好的單元測(cè)試。單元測(cè)試中不準(zhǔn)使用 System.out 來(lái)進(jìn)行人肉驗(yàn)證,必須使用 assert 來(lái)驗(yàn)證。
3)【強(qiáng)制】保持單元測(cè)試的獨(dú)立性。為了保證單元測(cè)試穩(wěn)定可靠且便于維護(hù),單元測(cè)試用例之間決不能互相調(diào)用,也不能依賴執(zhí)行的先后次序。
反例:method2 需要依賴 method1 的執(zhí)行,將執(zhí)行結(jié)果作為 method2 的輸入。
4)【強(qiáng)制】單元測(cè)試是可以重復(fù)執(zhí)行的,不能受到外界環(huán)境的影響。
說明:?jiǎn)卧獪y(cè)試通常會(huì)被放到持續(xù)集成中,每次有代碼 check in 時(shí)單元測(cè)試都會(huì)被執(zhí)行。如果單測(cè)對(duì)外部環(huán)境(網(wǎng)絡(luò)、服務(wù)、中間件等)有依賴,容易導(dǎo)致持續(xù)集成機(jī)制的不可用。
正例:為了不受外界環(huán)境影響,要求設(shè)計(jì)代碼時(shí)就把 SUT 的依賴改成注入,在測(cè)試時(shí)用 spring 這樣的 DI 框架注入一個(gè)本地(內(nèi)存)實(shí)現(xiàn)或者 Mock 實(shí)現(xiàn)。
5)【強(qiáng)制】對(duì)于單元測(cè)試,要保證測(cè)試粒度足夠小,有助于精確定位問題。單測(cè)粒度至多是類級(jí)別,一般是方法級(jí)別。
說明:只有測(cè)試粒度小才能在出錯(cuò)時(shí)盡快定位到出錯(cuò)位置。單測(cè)不負(fù)責(zé)檢查跨類或者跨系統(tǒng)的交互邏輯,那是集成測(cè)試的領(lǐng)域。
6)【強(qiáng)制】核心業(yè)務(wù)、核心應(yīng)用、核心模塊的增量代碼確保單元測(cè)試通過。
說明:新增代碼及時(shí)補(bǔ)充???元測(cè)試,如果新增代碼影響了原有單元測(cè)試,請(qǐng)及時(shí)修正。
7)【強(qiáng)制】單元測(cè)試代碼必須寫在如下工程目錄:src/test/java,不允許寫在業(yè)務(wù)代碼目錄下。
說明:源碼編譯時(shí)會(huì)跳過此目錄,而單元測(cè)試框架默認(rèn)是掃描此目錄。
8)【推薦】單元測(cè)試的基本目標(biāo):語(yǔ)句覆蓋率達(dá)到 70%;核心模塊的語(yǔ)句覆蓋率和分支覆蓋率都要達(dá)到 100%
說明:在工程規(guī)約的應(yīng)用分層中提到的 DAO 層,Manager 層,可重用度高的 Service,都應(yīng)該進(jìn)行單元測(cè)試。
9)【推薦】編寫單元測(cè)試代碼遵守 BCDE 原則,以保證被測(cè)試模塊的交付質(zhì)量。
B:Border,邊界值測(cè)試,包括循環(huán)邊界、特殊取值、特殊時(shí)間點(diǎn)、數(shù)據(jù)順序等。
C:Correct,正確的輸入,并得到預(yù)期的結(jié)果。
D:Design,與設(shè)計(jì)文檔相結(jié)合,來(lái)編寫單元測(cè)試。
E:Error,強(qiáng)制錯(cuò)誤信息輸入(如:非法數(shù)據(jù)、異常流程、業(yè)務(wù)允許外等),并得到預(yù)期的結(jié)果。
10)【推薦】對(duì)于數(shù)據(jù)庫(kù)相關(guān)的查詢,更新,刪除等操作,不能假設(shè)數(shù)據(jù)庫(kù)里的數(shù)據(jù)是存在的,或者直接操作數(shù)據(jù)庫(kù)把數(shù)據(jù)插入進(jìn)去,請(qǐng)使用程序插入或者導(dǎo)入數(shù)據(jù)的方式來(lái)準(zhǔn)備數(shù)據(jù)。
反例:刪除某一行數(shù)據(jù)的單元測(cè)試,在數(shù)據(jù)庫(kù)中,先直接手動(dòng)增加一行作為刪除目標(biāo),但是這一行新增數(shù)據(jù)并不符合業(yè)務(wù)插入規(guī)則,導(dǎo)致測(cè)試結(jié)果異常。
11)【推薦】和數(shù)據(jù)庫(kù)相關(guān)的單元測(cè)試,可以設(shè)定自動(dòng)回滾機(jī)制,不給數(shù)據(jù)庫(kù)造成臟數(shù)據(jù)?;蛘邔?duì)單元測(cè)試產(chǎn)生的數(shù)據(jù)有明確的前后綴標(biāo)識(shí)。
正例:在企業(yè)智能事業(yè)部的內(nèi)部單元測(cè)試中,使用 ENTERPRISE_INTELLIGENCE _UNIT_TEST_的前綴來(lái)標(biāo)識(shí)單元測(cè)試相關(guān)代碼。
12)【推薦】對(duì)于不可測(cè)的代碼在適當(dāng)?shù)臅r(shí)機(jī)做必要的重構(gòu),使代碼變得可測(cè),避免為了達(dá)到測(cè)試要求而書寫不規(guī)范測(cè)試代碼。
13)【推薦】在設(shè)計(jì)評(píng)審階段,開發(fā)人員需要和測(cè)試人員一起確定單元測(cè)試范圍,單元測(cè)試最好覆蓋所有測(cè)試用例。
14)【推薦】單元測(cè)試作為一種質(zhì)量保障手段,在項(xiàng)目提測(cè)前完成單元測(cè)試,不建議項(xiàng)目發(fā)布后補(bǔ)充單元測(cè)試用例。
15)【參考】為了更方便地進(jìn)行單元測(cè)試,業(yè)務(wù)代碼應(yīng)避免以下情況:
構(gòu)造方法中做的事情過多。
存在過多的全局變量和靜態(tài)方法。
存在過多的外部依賴。
存在過多的條件語(yǔ)句。
說明:多層條件語(yǔ)句建議使用衛(wèi)語(yǔ)句、策略模式、狀態(tài)模式等方式重構(gòu)。
16)【參考】不要對(duì)單元測(cè)試存在如下誤解:
那是測(cè)試同學(xué)干的事情。本文是開發(fā)手冊(cè),凡是本文內(nèi)容都是與開發(fā)同學(xué)強(qiáng)相關(guān)的。
單元測(cè)試代碼是多余的。系統(tǒng)的整體功能與各單元部件的測(cè)試正常與否是強(qiáng)相關(guān)的。
單元測(cè)試代碼不需要維護(hù)。一年半載后,那么單元測(cè)試幾乎處于廢棄狀態(tài)。
單元測(cè)試與線上故障沒有辯證關(guān)系。好的單元測(cè)試能夠最大限度地規(guī)避線上故障。
13、安全規(guī)約
13.1、隸屬于用戶個(gè)人的頁(yè)面或者功能必須進(jìn)行權(quán)限控制校驗(yàn)。
說明:防止沒有做水平權(quán)限校驗(yàn)就可隨意訪問、修改、刪除別人的數(shù)據(jù),比如查看他人的私信內(nèi)容、修改他人的訂單。
13.2、用戶敏感數(shù)據(jù)禁止直接展示,必須對(duì)展示數(shù)據(jù)進(jìn)行脫敏。
說明:中國(guó)大陸個(gè)人手機(jī)號(hào)碼顯示為:137****0969,隱藏中間 4 位,防止隱私泄露。
13.3、用戶輸入的 SQL 參數(shù)嚴(yán)格使用參數(shù)綁定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 訪問數(shù)據(jù)庫(kù)。
13.4、用戶請(qǐng)求傳入的任何參數(shù)必須做有效性驗(yàn)證。
說明:忽略參數(shù)校驗(yàn)可能導(dǎo)致:
page size 過大導(dǎo)致內(nèi)存溢出
惡意 order by 導(dǎo)致數(shù)據(jù)庫(kù)慢查詢
任意重定向
SQL 注入
反序列化注入
正則輸入源串拒絕服務(wù) ReDoS
說明:Java 代碼用正則來(lái)驗(yàn)證客戶端的輸入,有些正則寫法驗(yàn)證普通用戶輸入沒有問題,但是如果攻擊人員使用的是特殊構(gòu)造的字符串來(lái)驗(yàn)證,有可能導(dǎo)致死循環(huán)的結(jié)果。
13.5、禁止向 HTML 頁(yè)面輸出未經(jīng)安全過濾或未正確轉(zhuǎn)義的用戶數(shù)據(jù)。
13.6、表單、AJAX 提交必須執(zhí)行 CSRF 安全驗(yàn)證。
說明:CSRF(Cross-site request forgery)跨站請(qǐng)求偽造是一類常見編程漏洞。對(duì)于存在 CSRF 漏洞的應(yīng)用/網(wǎng)站,攻擊者可以事先構(gòu)造好 URL,只要受害者用戶一訪問,后臺(tái)便在用戶不知情的情況下對(duì)數(shù)據(jù)庫(kù)中用戶參數(shù)進(jìn)行相應(yīng)修改。
13.7、在使用平臺(tái)資源,譬如短信、郵件、電話、下單、支付,必須實(shí)現(xiàn)正確的防重放的 機(jī)制,如數(shù)量限制、疲勞度控制、驗(yàn)證碼校驗(yàn),避免被濫刷而導(dǎo)致資損。
說明:如注冊(cè)時(shí)發(fā)送驗(yàn)證碼到手機(jī),如果沒有限制次數(shù)和頻率,那么可以利用此功能騷擾到其它用戶,并造成短信平臺(tái)資源浪費(fèi)。
13.8、發(fā)貼、評(píng)論、發(fā)送即時(shí)消息等用戶生成內(nèi)容的場(chǎng)景必須實(shí)現(xiàn)防刷、文本內(nèi)容違禁詞過濾等風(fēng)控策略。
14、MySQL 數(shù)據(jù)庫(kù)
14.1、建表規(guī)約
表達(dá)是與否概念的字段,必須使用 is_xxx 的方式命名,數(shù)據(jù)類型是 unsigned tinyint(1 表示是,0 表示否)。
說明:任何字段如果為非負(fù)數(shù),必須是 unsigned。
注意:POJO 類中的任何布爾類型的變量,都不要加 is 前綴,所以,需要在設(shè)置從 is_xxx 到 Xxx 的映射關(guān)系。數(shù)據(jù)庫(kù)表示是與否的值,使用 tinyint 類型,堅(jiān)持 is_xxx 的命名方式是為了明確其取值含義與取值范圍。
正例:表達(dá)邏輯刪除的字段名 is_deleted,1 表示刪除,0 表示未刪除。
14.2、表名、字段名必須使用小寫字母或數(shù)字,禁止出現(xiàn)數(shù)字開頭,禁止兩個(gè)下劃線中間只出現(xiàn)數(shù)字。數(shù)據(jù)庫(kù)字段名的修改代價(jià)很大,因?yàn)闊o(wú)法進(jìn)行預(yù)發(fā)布,所以字段名稱需要慎重考慮。
14.3、 說明:MySQL 在 Windows 下不區(qū)分大小寫,但在 Linux 下默認(rèn)是區(qū)分大小寫。因此,數(shù)據(jù)庫(kù)名、表名、字段名,都不允許出現(xiàn)任何大寫字母,避免節(jié)外生枝。
正例:aliyun_admin,rdc_config,level3_name
反例:AliyunAdmin,rdcConfig,level_3_name
14.4、 表名不使用復(fù)數(shù)名詞。
說明:表名應(yīng)該僅僅表示表里面的實(shí)體內(nèi)容,不應(yīng)該表示實(shí)體數(shù)量,對(duì)應(yīng)于 DO 類名也是單數(shù)形式,符合表達(dá)習(xí)慣。
14.5、禁用保留字,如 desc、range、match、delayed 等,請(qǐng)參考 MySQL 官方保留字。
14.6、主鍵索引名為 pk_字段名;唯一索引名為 uk_字段名;普通索引名則為 idx_字段名。
說明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的簡(jiǎn)稱。
14.7、小數(shù)類型為 decimal,禁止使用 float 和 double。
說明:在存儲(chǔ)的時(shí)候,float 和 double 都存在精度損失的問題,很可能在比較值的時(shí)候,得到不正確的結(jié)果。如果存儲(chǔ)的數(shù)據(jù)范圍超過 decimal 的范圍,建議將數(shù)據(jù)拆成整數(shù)和小數(shù)并分開存儲(chǔ)。
8)【強(qiáng)制】如果存儲(chǔ)的字符串長(zhǎng)度幾乎相等,使用 char 定長(zhǎng)字符串類型。
9)【強(qiáng)制】varchar 是可變長(zhǎng)字符串,不預(yù)先分配存儲(chǔ)空間,長(zhǎng)度不要超過 5000,如果存儲(chǔ)長(zhǎng)度大于此值,定義字段類型為 text,獨(dú)立出來(lái)一張表,用主鍵來(lái)對(duì)應(yīng),避免影響其它字段索引效率。
10)【強(qiáng)制】表必備三字段:id, create_time, update_time。
說明:其中 id 必為主鍵,類型為 bigint unsigned、單表時(shí)自增、步長(zhǎng)為 1。create_time, update_time 的類型均為 datetime 類型。
11)【推薦】表的命名最好是遵循“業(yè)務(wù)名稱_表的作用”。
正例:alipay_task / force_project / trade_config
12)【推薦】庫(kù)名與應(yīng)用名稱盡量一致。
13)【推薦】如果修改字段含義或?qū)ψ侄伪硎镜臓顟B(tài)追加時(shí),需要及時(shí)更新字段注釋。
14)【推薦】字段允許適當(dāng)冗余,以提高查詢性能,但必須考慮數(shù)據(jù)一致。冗余字段應(yīng)遵循:
不是頻繁修改的字段。
不是 varchar 超長(zhǎng)字段,更不能是 text 字段。
不是唯一索引的字段。
正例:商品類目名稱使用頻率高,字段長(zhǎng)度短,名稱基本一不變,可在相關(guān)聯(lián)的表中冗余存儲(chǔ)類目名稱,避免關(guān)聯(lián)查詢。
15)【推薦】單表行數(shù)超過 500 萬(wàn)行或者單表容量超過 2GB,才推薦進(jìn)行分庫(kù)分表。
說明:如果預(yù)計(jì)三年后的數(shù)據(jù)量根本達(dá)不到這個(gè)級(jí)別,請(qǐng)不要在創(chuàng)建表時(shí)就分庫(kù)分表。
16)【參考】合適的字符存儲(chǔ)長(zhǎng)度,不但節(jié)約數(shù)據(jù)庫(kù)表空間、節(jié)約索引存儲(chǔ),更重要的是提升檢索速度。
正例:如下表,其中無(wú)符號(hào)值可以避免誤存負(fù)數(shù),且擴(kuò)大了表示范圍。
對(duì)象 年齡區(qū)間 類型 字節(jié) 表示范圍
人 150 歲之內(nèi) tinyint unsigned 1 無(wú)符號(hào)值:0 到 255
龜 數(shù)百歲 smallint unsigned 2 無(wú)符號(hào)值:0 到 65535
恐龍化石 數(shù)千萬(wàn)年 int unsigned 4 無(wú)符號(hào)值:0 到約 42.9 億
太陽(yáng) 約 50 億年 bigint unsigned 8 無(wú)符號(hào)值:0 到約 10 的 19 次方
14.2 、 索引規(guī)約
1)【強(qiáng)制】業(yè)務(wù)上具有唯一特性的字段,即使是多個(gè)字段的組合,也必須建成唯一索引。
說明:不要以為唯一索引影響了 insert 速度,這個(gè)速度損耗可以忽略,但提高查找速度是明顯的;另外,即使在應(yīng)用層做了非常完善的校驗(yàn)控制,只要沒有唯一索引,根據(jù)墨菲定律,必然有臟數(shù)據(jù)產(chǎn)生。
2)【強(qiáng)制】超過三個(gè)表禁止 join。需要 join 的字段,數(shù)據(jù)類型必須絕對(duì)一致;多表關(guān)聯(lián)查詢時(shí),保證被關(guān)聯(lián)的字段需要有索引。
說明:即使雙表 join 也要注意表索引、SQL 性能。
3)【強(qiáng)制】在 varchar 字段上建立索引時(shí),必須指定索引長(zhǎng)度,沒必要對(duì)全字段建立索引,根據(jù)實(shí)際文本區(qū)分度決定索引長(zhǎng)度即可。
說明:索引的長(zhǎng)度與區(qū)分度是一對(duì)矛盾體,一般對(duì)字符串類型數(shù)據(jù),長(zhǎng)度為 20 的索引,區(qū)分度會(huì)高達(dá) 90% 以上,可以使用 count(distinct left(列名, 索引長(zhǎng)度))/count(*)的區(qū)分度來(lái)確定。
4)【強(qiáng)制】頁(yè)面搜索嚴(yán)禁左模糊或者全模糊,如果需要請(qǐng)走搜索引擎來(lái)解決。
說明:索引文件具有 B-Tree 的最左前綴匹配特性,如果左邊的值未確定,那么無(wú)法使用此索引。
5)【推薦】如果有 order by 的場(chǎng)景,請(qǐng)注意利用索引的有序性。order by 最后的字段是組合索引的一部分,并且放在索引組合順序的最后,避免出現(xiàn) file_sort 的情況,影響查詢性能。
正例:where a=? and b=? order by c; 索引:a_b_c
反例:索引如果存在范圍查詢,那么索引有序性無(wú)法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 無(wú)法排序。
6)【推薦】利用覆蓋索引來(lái)進(jìn)行查詢操作,避免回表。
說明:如果一本書需要知道第 11 章是什么標(biāo)題,會(huì)翻開第 11 章對(duì)應(yīng)的那一頁(yè)嗎?目錄瀏覽一下就好,這個(gè)目錄就是起到覆蓋索引的作用。
正例:能夠建立索引的種類分為主鍵索引、唯一索引、普通索引三種,而覆蓋索引只是一種查詢的一種效果,用 explain 的結(jié)果,extra 列會(huì)出現(xiàn):using index。
7)【推薦】利用延遲關(guān)聯(lián)或者子查詢優(yōu)化超多分頁(yè)場(chǎng)景。
說明:MySQL 并不是跳過 offset 行,而是取 offset+N 行,然后返回放棄前 offset 行,返回 N 行,那當(dāng)offset 特別大的時(shí)候,效率就非常的低下,要么控制返回的總頁(yè)數(shù),要么對(duì)超過特定閾值的頁(yè)數(shù)進(jìn)行 SQL 改寫。
正例:先快速定位需要獲取的 id 段,然后再關(guān)聯(lián):
8)SELECT a.* FROM 表 1 a, (select id from 表 1 where 條件 LIMIT 100000,20 ) b where a.id=b.id
【推薦】SQL 性能優(yōu)化的目標(biāo):至少要達(dá)到 range 級(jí)別,要求是 ref 級(jí)別,如果可以是 consts 最好。
說明:
consts 單表中最多只有一個(gè)匹配行(主鍵或者唯一索引),在優(yōu)化階段即可讀取到數(shù)據(jù)。
ref 指的是使用普通的索引(normal index)。
range 對(duì)索引進(jìn)行范圍檢索。
反例:explain 表的結(jié)果,type=index,索引物理文件全掃描,速度非常慢,這個(gè) index 級(jí)別比較 range 還低,與全表掃描是小巫見大巫。
9)【推薦】建組合索引的時(shí)候,區(qū)分度最高的在最左邊。
正例:如果 where a=? and b=? ,如果 a 列的幾乎接近于唯一值,那么只需要單建 idx_a 索引即可。
說明:存在非等號(hào)和等號(hào)混合時(shí),在建索引時(shí),請(qǐng)把等號(hào)條件的列前置。如:where c>? and d=? 那么即使 c 的區(qū)分度更高,也必須把 d 放在索引的最前列,即索引 idx_d_c。
10)【推薦】防止因字段類型不同造成的隱式轉(zhuǎn)換,導(dǎo)致索引失效。
【參考】創(chuàng)建索引時(shí)避免有如下極端誤解:
寧濫勿缺。認(rèn)為一個(gè)查詢就需要建一個(gè)索引。
寧缺勿濫。認(rèn)為索引會(huì)消耗空間、嚴(yán)重拖慢記錄的更新以及行的新增速度。
抵制惟一索引。認(rèn)為業(yè)務(wù)的惟一性一律需要在應(yīng)用層通過“先查后插”方式解決。
14.3、SQL 語(yǔ)句
1)【強(qiáng)制】不要使用 count(列名)或 count(常量)來(lái)替代 count(),count()是 SQL92 定義的標(biāo)準(zhǔn)統(tǒng)計(jì)行數(shù)的語(yǔ)法,跟數(shù)據(jù)庫(kù)無(wú)關(guān),跟 NULL 和非 NULL 無(wú)關(guān)。
說明:count(*)會(huì)統(tǒng)計(jì)值為 NULL 的行,而 count(列名)不會(huì)統(tǒng)計(jì)此列為 NULL 值的行。
2)【強(qiáng)制】count(distinct col) 計(jì)算該列除 NULL 之外的不重復(fù)行數(shù),注意 count(distinct col1, col2) 如果其中一列全為 NULL,那么即使另一列有不同的值,也返回為 0。
3)【強(qiáng)制】當(dāng)某一列的值全是 NULL 時(shí),count(col)的返回結(jié)果為 0,但 sum(col)的返回結(jié)果為 NULL,因此使用 sum()時(shí)需注意 NPE 問題。
正例:使用如下方式來(lái)避免 sum 的 NPE 問題:SELECT IFNULL(SUM(column), 0) FROM table;
4)【強(qiáng)制】使用 ISNULL()來(lái)判斷是否為 NULL 值。
說明:NULL 與任何值的直接比較都為 NULL。
NULL<>NULL 的返回結(jié)果是 NULL,而不是 false。
NULL=NULL 的返回結(jié)果是 NULL,而不是 true。
NULL<>1 的返回結(jié)果是 NULL,而不是 true。
5)【強(qiáng)制】代碼中寫分頁(yè)查詢邏輯時(shí),若 count 為 0 應(yīng)直接返回,避免執(zhí)行后面的分頁(yè)語(yǔ)句。
【強(qiáng)制】不得使用外鍵與級(jí)聯(lián),一切外鍵概念必須在應(yīng)用層解決。
說明:以學(xué)生和成績(jī)的關(guān)系為例,學(xué)生表中的 student_id 是主鍵,那么成績(jī)表中的 student_id 則為外鍵。如果更新學(xué)生表中的 student_id,同時(shí)觸發(fā)成績(jī)表中的 student_id 更新,即為級(jí)聯(lián)更新。外鍵與級(jí)聯(lián)更新適用于單機(jī)低并發(fā),不適合分布式、高并發(fā)集群;級(jí)聯(lián)更新是強(qiáng)阻塞,存在數(shù)據(jù)庫(kù)更新風(fēng)暴的風(fēng)險(xiǎn);外鍵影響數(shù)據(jù)庫(kù)的插入速度。
6)【強(qiáng)制】禁止使用存儲(chǔ)過程,存儲(chǔ)過程難以調(diào)試和擴(kuò)展,更沒有移植性。
7)【強(qiáng)制】數(shù)據(jù)訂正(特別是刪除、修改記錄操作)時(shí),要先 select,避免出現(xiàn)誤刪除,確認(rèn)無(wú)誤才能執(zhí)行更新語(yǔ)句。
8)【推薦】in 操作能避免則避免,若實(shí)在避免不了,需要仔細(xì)評(píng)估 in 后邊的集合元素?cái)?shù)量,控制在 1000 個(gè)之內(nèi)。
9)【參考】如果有國(guó)際化需要,所有的字符存儲(chǔ)與表示,均以 utf-8 編碼,注意字符統(tǒng)計(jì)函數(shù)的區(qū)別。
說明:
SELECT LENGTH(“輕松工作”); 返回為 12
SELECT CHARACTER_LENGTH(“輕松工作”); 返回為 4
如果需要存儲(chǔ)表情,那么選擇 utf8mb4 來(lái)進(jìn)行存儲(chǔ),注意它與 utf-8 編碼的區(qū)別。
10)【參考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系統(tǒng)和事務(wù)日志資源少,但 TRUNCATE 無(wú)事務(wù)且不觸發(fā) trigger,有可能造成事故,故不建議在開發(fā)代碼中使用此語(yǔ)句。
說明:TRUNCATE TABLE 在功能上與不帶 WHERE 子句的 DELETE 語(yǔ)句相同。
14.4、ORM 映射
1)【強(qiáng)制】在表查詢中,一律不要使用 * 作為查詢的字段列表,需要哪些字段必須明確寫明。
說明:
增加查詢分析器解析成本。
增減字段容易與 resultMap 配置不一致。
無(wú)用字段增加網(wǎng)絡(luò)消耗,尤其是 text 類型的字段。
2)【強(qiáng)制】POJO 類的布爾屬性不能加 is,而數(shù)據(jù)庫(kù)字段必須加 is_,要求在 resultMap 中進(jìn)行字段與屬性之間的映射。
說明:參見定義 POJO 類以及數(shù)據(jù)庫(kù)字段定義規(guī)定,在中增加映射,是必須的。在 MyBatis Generator 生成的代碼中,需要進(jìn)行對(duì)應(yīng)的修改。
3)【強(qiáng)制】不要用 resultClass 當(dāng)返回參數(shù),即使所有類屬性名與數(shù)據(jù)庫(kù)字段一一對(duì)應(yīng),也需要定義;反過來(lái),每一個(gè)表也必然有一個(gè) POJO 類與之對(duì)應(yīng)。
說明:配置映射關(guān)系,使字段與 DO 類解耦,方便維護(hù)。
4)【強(qiáng)制】sql.xml 配置參數(shù)使用:#{},#param# 不要使用${} 此種方式容易出現(xiàn) SQL 注入。
【強(qiáng)制】iBATIS 自帶的 queryForList(String statementName,int start,int size)不推薦使用。
說明:其實(shí)現(xiàn)方式是在數(shù)據(jù)庫(kù)取到 statementName 對(duì)應(yīng)的 SQL 語(yǔ)句的所有記錄,再通過 subList 取 start,size 的子集合。
正例:
Map<String, Object> map = new HashMap<>();
map.put(“start”, start);
map.put(“size”, size);
5)【強(qiáng)制】不允許直接拿 HashMap 與 Hashtable 作為查詢結(jié)果集的輸出。
說明:resultClass=”Hashtable”,會(huì)置入字段名和屬性值,但是值的類型不可控。
6)【強(qiáng)制】更新數(shù)據(jù)表記錄時(shí),必須同時(shí)更新記錄對(duì)應(yīng)的 gmt_modified 字段值為當(dāng)前時(shí)間。
【推薦】不要寫一個(gè)大而全的數(shù)據(jù)更新接口。傳入為 POJO 類,不管是不是自己的目標(biāo)更新字段,都進(jìn)行 update table set c1=value1,c2=value2,c3=value3; 這是不對(duì)的。執(zhí)行 SQL 時(shí),不要更新無(wú)改動(dòng)的字段,一是易出錯(cuò);二是效率低;三是增加 binlog 存儲(chǔ)。
7)【參考】@Transactional 事務(wù)不要濫用。事務(wù)會(huì)影響數(shù)據(jù)庫(kù)的 QPS,另外使用事務(wù)的地方需要考慮各方面的回滾方案,包括緩存回滾、搜索引擎回滾、消息補(bǔ)償、統(tǒng)計(jì)修正等。
8)【參考】中的 compareValue 是與屬性值對(duì)比的常量,一般是數(shù)字,表示相等時(shí)帶上此條件;表示不為空且不為 null 時(shí)執(zhí)行;表示不為 null 值時(shí)執(zhí)行。
15、工程結(jié)構(gòu)
15.1、 應(yīng)用分層
1)【推薦】圖中默認(rèn)上層依賴于下層,箭頭關(guān)系表示可直接依賴,如:開放接口層可以依賴于 Web 層,也可以直接依賴于 Service 層,依此類推:
開放接口層:可直接封裝 Service 方法暴露成 RPC 接口;通過 Web 封裝成 http 接口;進(jìn)行網(wǎng)關(guān)安 全控制、流量控制等。
終端顯示層:各個(gè)端的模板渲染并執(zhí)行顯示的層。當(dāng)前主要是 velocity 渲染,JS 渲染,JSP 渲染,移動(dòng)端展示等。
Web 層:主要是對(duì)訪問控制進(jìn)行轉(zhuǎn)發(fā),各類基本參數(shù)校驗(yàn),或者不復(fù)用的業(yè)務(wù)簡(jiǎn)單處理等。
Service 層:相對(duì)具體的業(yè)務(wù)邏輯服務(wù)層。
Manager 層:通用業(yè)務(wù)處理層,它有如下特征:
對(duì)第三方平臺(tái)封裝的層,預(yù)處理返回結(jié)果及轉(zhuǎn)化異常信息。
對(duì) Service 層通用能力的下沉,如緩存方案、中間件通用處理。
與 DAO 層交互,對(duì)多個(gè) DAO 的組合復(fù)用。
DAO 層:數(shù)據(jù)訪問層,與底層 MySQL、Oracle、Hbase 等進(jìn)行數(shù)據(jù)交互。
外部接口或第三方平臺(tái):包括其它部門 RPC 開放接口,基礎(chǔ)平臺(tái),其它公司的 HTTP 接口。
2)【參考】(分層異常處理規(guī)約)在 DAO 層,產(chǎn)生的異常類型有很多,無(wú)法用細(xì)粒度的異常進(jìn)行 catch,使用 catch(Exception e)方式,并 throw new DAOException(e),不需要打印日志,因?yàn)槿罩驹?Manager/Service 層一定需要捕獲并打印到日志文件中去,如果同臺(tái)服務(wù)器再打日志,浪費(fèi)性能和存儲(chǔ)。在 Service 層出現(xiàn)異常時(shí),必須記錄出錯(cuò)日志到磁盤,盡可能帶上參數(shù)信息,相當(dāng)于保護(hù)案發(fā)現(xiàn)場(chǎng)。如果 Manager 層與 Service 同機(jī)部署,日志方式與 DAO 層處理一致,如果是單獨(dú)部署,則采用與 Service 一致的處理方式。Web 層絕不應(yīng)該繼續(xù)往上拋異常,因?yàn)橐呀?jīng)處于頂層,如果意識(shí)到這個(gè)異常將導(dǎo)致頁(yè)面無(wú)法正常渲染,那么就應(yīng)該直接跳轉(zhuǎn)到友好錯(cuò)誤頁(yè)面,加上用戶容易理解的錯(cuò)誤提示信息。開放接口層要將異常處理成錯(cuò)誤碼
和錯(cuò)誤信息方式返回。
3)【參考】分層領(lǐng)域模型規(guī)約:
DO(Data Object):此對(duì)象與數(shù)據(jù)庫(kù)表結(jié)構(gòu)一一對(duì)應(yīng),通過 DAO 層向上傳輸數(shù)據(jù)源對(duì)象。
DTO(Data Transfer Object):數(shù)據(jù)傳輸對(duì)象,Service 或 Manager 向外傳輸?shù)膶?duì)象。
BO(Business Object):業(yè)務(wù)對(duì)象,由 Service 層輸出的封裝業(yè)務(wù)邏輯的對(duì)象。
AO(Application Object):應(yīng)用對(duì)象,在 Web 層與 Service 層之間抽象的復(fù)用對(duì)象模型,極為貼近展示層,復(fù)用度不高。
VO(View Object):顯示層對(duì)象,通常是 Web 向模板渲染引擎層傳輸?shù)膶?duì)象。
Query:數(shù)據(jù)查詢對(duì)象,各層接收上層的查詢請(qǐng)求。注意超過 2 個(gè)參數(shù)的查詢封裝,禁止使用 Map 類來(lái)傳輸。
15.2、二方庫(kù)依賴
1)【強(qiáng)制】定義 GAV 遵從以下規(guī)則:
GroupID 格式:com.{公司/BU }.業(yè)務(wù)線 [.子業(yè)務(wù)線],最多 4 級(jí)。
說明:{公司/BU} 例如:alibaba/taobao/tmall/aliexpress 等 BU 一級(jí);子業(yè)務(wù)線可選。
正例:com.taobao.jstorm 或 com.alibaba.dubbo.register
ArtifactID 格式:產(chǎn)品線名-模塊名。語(yǔ)義不重復(fù)不遺漏,先到中央倉(cāng)庫(kù)去查證一下。
正例:dubbo-client / fastjson-api / jstorm-tool
Version:詳細(xì)規(guī)定參考下方。
2)【強(qiáng)制】二方庫(kù)版本號(hào)命名方式:主版本號(hào).次版本號(hào).修訂號(hào)
主版本號(hào):產(chǎn)品方向改變,或者大規(guī)模 API 不兼容,或者架構(gòu)不兼容升級(jí)。
次版本號(hào):保持相對(duì)兼容性,增加主要功能特性,影響范圍極小的 API 不兼容修改。
修訂號(hào):保持完全兼容性,修復(fù) BUG、新增次要功能特性等。
說明:注意起始版本號(hào)必須為:1.0.0,而不是 0.0.1,正式發(fā)布的類庫(kù)必須先去中央倉(cāng)庫(kù)進(jìn)行查證,使版本號(hào)有延續(xù)性,正式版本號(hào)不允許覆蓋升級(jí)。如當(dāng)前版本:1.3.3,那么下一個(gè)合理的版本號(hào):1.3.4 或 1.4.0 或 2.0.0
3)【強(qiáng)制】線上應(yīng)用不要依賴 SNAPSHOT 版本(安全包除外)。
說明:不依賴 SNAPSHOT 版本是保證應(yīng)用發(fā)布的冪等性。另外,也可以加快編譯時(shí)的打包構(gòu)建。
4)【強(qiáng)制】二方庫(kù)的新增或升級(jí),保持除功能點(diǎn)之外的其它 jar 包仲裁結(jié)果不變。如果有改變,必須明確評(píng)估和驗(yàn)證。
說明:在升級(jí)時(shí),進(jìn)行 dependency:resolve 前后信息比對(duì),如果仲裁結(jié)果完全不一致,那么通過 dependency:tree 命令,找出差異點(diǎn),進(jìn)行排除 jar 包。
5)【強(qiáng)制】二方庫(kù)里可以定義枚舉類型,參數(shù)可以使用枚舉類型,但是接口返回值不允許使用枚舉類型或者包含枚舉類型的 POJO 對(duì)象。
6)【強(qiáng)制】依賴于一個(gè)二方庫(kù)群時(shí),必須定義一個(gè)統(tǒng)一的版本變量,避免版本號(hào)不一致。
說明:依賴 springframework-core,-context,-beans,它們都是同一個(gè)版本,可以定義一個(gè)變量來(lái)保存版本:${spring.version},定義依賴的時(shí)候,引用該版本。
7)【強(qiáng)制】禁止在子項(xiàng)目的 pom 依賴中出現(xiàn)相同的 GroupId,相同的 ArtifactId,但是不同的 Version。
說明:在本地調(diào)試時(shí)會(huì)使用各子項(xiàng)目指定的版本號(hào),但是合并成一個(gè) war,只能有一個(gè)版本號(hào)出現(xiàn)在最后的 lib 目錄中。可能出現(xiàn)線下調(diào)試是正確的,發(fā)布到線上卻出故障的問題。
8)【推薦】底層基礎(chǔ)技術(shù)框架、核心數(shù)據(jù)管理平臺(tái)、或近硬件端系統(tǒng)謹(jǐn)慎引入第三方實(shí)現(xiàn)。
9)【推薦】所有 pom 文件中的依賴聲明放在語(yǔ)句塊中,所有版本仲裁放在語(yǔ)句塊中。
說明:里只是聲明版本,并不實(shí)現(xiàn)引入,因此子項(xiàng)目需要顯式的聲明依賴,version 和 scope 都讀取自父 pom。而所有聲明在主 pom 的里的依賴都會(huì)自動(dòng)引入,并默認(rèn)被所有的子項(xiàng)目繼承。
10)【推薦】二方庫(kù)不要有配置項(xiàng),最低限度不要再增加配置項(xiàng)
11)【參考】為避免應(yīng)用二方庫(kù)的依賴沖突問題,二方庫(kù)發(fā)布者應(yīng)當(dāng)遵循以下原則:
精簡(jiǎn)可控原則。移除一切不必要的 API 和依賴,只包含 Service API、必要的領(lǐng)域模型對(duì)象、Utils 類、常量、枚舉等。如果依賴其它二方庫(kù),盡量是 provided 引入,讓二方庫(kù)使用者去依賴具體版本號(hào); 無(wú) log 具體實(shí)現(xiàn),只依賴日志框架。
穩(wěn)定可追溯原則。每個(gè)版本的變化應(yīng)該被記錄,二方庫(kù)由誰(shuí)維護(hù),源碼在哪里,都需要能方便查到。除非用戶主動(dòng)升級(jí)版本,否則公共二方庫(kù)的行為不應(yīng)該發(fā)生變化。
16、服務(wù)器
16.1、高并發(fā)服務(wù)器建議調(diào)小 TCP 協(xié)議的 time_wait 超時(shí)時(shí)間。
說明:操作系統(tǒng)默認(rèn) 240 秒后,才會(huì)關(guān)閉處于 time_wait 狀態(tài)的連接,在高并發(fā)訪問下,服務(wù)器端會(huì)因?yàn)樘幱?time_wait 的連接數(shù)太多,可能無(wú)法建立新的連接,所以需要在服務(wù)器上調(diào)小此等待值。
正例:在 linux 服務(wù)器上請(qǐng)通過變更/etc/sysctl.conf 文件去修改該缺省值(秒):
net.ipv4.tcp_fin_timeout = 30
16.2、調(diào)大服務(wù)器所支持的最大文件句柄數(shù)(File Descriptor,簡(jiǎn)寫為 fd)。
說明:主流操作系統(tǒng)的設(shè)計(jì)是將 TCP/UDP 連接采用與文件一樣的方式去管理,即一個(gè)連接對(duì)應(yīng)于一個(gè) fd。主流的 linux 服務(wù)器默認(rèn)所支持最大 fd 數(shù)量為 1024,當(dāng)并發(fā)連接數(shù)很大時(shí)很容易因?yàn)?fd 不足而出現(xiàn)“open too many files”錯(cuò)誤,導(dǎo)致新的連接無(wú)法建立。建議將 linux 服務(wù)器所支持的最大句柄數(shù)調(diào)高數(shù)倍(與服務(wù)器的內(nèi)存數(shù)量相關(guān))。
16.3、給 JVM 環(huán)境參數(shù)設(shè)置-XX:+HeapDumpOnOutOfMemoryError 參數(shù),讓 JVM 碰到 OOM 場(chǎng)景時(shí)輸出 dump 信息。
說明:OOM 的發(fā)生是有概率的,甚至相隔數(shù)月才出現(xiàn)一例,出錯(cuò)時(shí)的堆內(nèi)信息對(duì)解決問題非常有幫助。
16.4、在線上生產(chǎn)環(huán)境,JVM 的 Xms 和 Xmx 設(shè)置一樣大小的內(nèi)存容量,避免在 GC 后調(diào)整堆大小帶來(lái)的壓力。
16.5、【參考】服務(wù)器內(nèi)部重定向使用 forward;外部重定向地址使用 URL 拼裝工具類來(lái)生成,否則會(huì)帶來(lái) URL 維護(hù)不一致的問題和潛在的安全風(fēng)險(xiǎn)。
17、設(shè)計(jì)規(guī)約
17.1、存儲(chǔ)方案和底層數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)獲得評(píng)審一致通過,并沉淀成為文檔。
說明:有缺陷的底層數(shù)據(jù)結(jié)構(gòu)容易導(dǎo)致系統(tǒng)風(fēng)險(xiǎn)上升,可擴(kuò)展性下降,重構(gòu)成本也會(huì)因歷史數(shù)據(jù)遷移和系統(tǒng)平滑過渡而陡然增加,所以,存儲(chǔ)方案和數(shù)據(jù)結(jié)構(gòu)需要認(rèn)真地進(jìn)行設(shè)計(jì)和評(píng)審,生產(chǎn)環(huán)境提交執(zhí)行后,需要進(jìn)行 double check。
正例:評(píng)審內(nèi)容包括存儲(chǔ)介質(zhì)選型、表結(jié)構(gòu)設(shè)計(jì)能否滿足技術(shù)方案、存取性能和存儲(chǔ)空間能否滿足業(yè)務(wù)發(fā)展、表或字段之間的辯證關(guān)系、字段名稱、字段類型、索引等;數(shù)據(jù)結(jié)構(gòu)變更(如在原有表中新增字段)也需要進(jìn)行評(píng)審?fù)ㄟ^后上線。
17.2、在需求分析階段,如果與系統(tǒng)交互的 User 超過一類并且相關(guān)的 User Case 超過 5 個(gè),使用用例圖來(lái)表達(dá)更加清晰的結(jié)構(gòu)化需求。
17.3、如果某個(gè)業(yè)務(wù)對(duì)象的狀態(tài)超過 3 個(gè),使用狀態(tài)圖來(lái)表達(dá)并且明確狀態(tài)變化的各個(gè)觸發(fā)條件。
說明:狀態(tài)圖的核心是對(duì)象狀態(tài),首先明確對(duì)象有多少種狀態(tài),然后明確兩兩狀態(tài)之間是否存在直接轉(zhuǎn)換關(guān)系,再明確觸發(fā)狀態(tài)轉(zhuǎn)換的條件是什么。
正例:淘寶訂單狀態(tài)有已下單、待付款、已付款、待發(fā)貨、已發(fā)貨、已收貨等。比如已下單與已收貨這兩種狀態(tài)之間是不可能有直接轉(zhuǎn)換關(guān)系的。
17.4、如果系統(tǒng)中某個(gè)功能的調(diào)用鏈路上的涉及對(duì)象超過 3 個(gè),使用時(shí)序圖來(lái)表達(dá)并且明確各調(diào)用環(huán)節(jié)的輸入與輸出。
說明:時(shí)序圖反映了一系列對(duì)象間的交互與協(xié)作關(guān)系,清晰立體地反映系統(tǒng)的調(diào)用縱深鏈路。
17.5、如果系統(tǒng)中模型類超過 5 個(gè),并且存在復(fù)雜的依賴關(guān)系,使用類圖來(lái)表達(dá)并且明確類之間的關(guān)系。
說明:類圖像建筑領(lǐng)域的施工圖,如果搭平房,可能不需要,但如果建造螞蟻 Z 空間大樓,肯定需要詳細(xì)的施工圖。
17.6、如果系統(tǒng)中超過 2 個(gè)對(duì)象之間存在協(xié)作關(guān)系,并且需要表示復(fù)雜的處理流程,使用活動(dòng)圖來(lái)表示。
說明:活動(dòng)圖是流程圖的擴(kuò)展,增加了能夠體現(xiàn)協(xié)作關(guān)系的對(duì)象泳道,支持表示并發(fā)等。
17.7、需求分析與系統(tǒng)設(shè)計(jì)在考慮主干功能的同時(shí),需要充分評(píng)估異常流程與業(yè)務(wù)邊界。
反例:用戶在淘寶付款過程中,銀行扣款成功,發(fā)送給用戶扣款成功短信,但是支付寶入款時(shí)由于斷網(wǎng)演練產(chǎn)生異常,淘寶訂單頁(yè)面依然顯示未付款,導(dǎo)致用戶投訴。
17.8、類在設(shè)計(jì)與實(shí)現(xiàn)時(shí)要符合單一原則。
說明:?jiǎn)我辉瓌t最易理解卻是最難實(shí)現(xiàn)的一條規(guī)則,隨著系統(tǒng)演進(jìn),很多時(shí)候,忘記了類設(shè)計(jì)的初衷。
17.9、謹(jǐn)慎使用繼承的方式來(lái)進(jìn)行擴(kuò)展,優(yōu)先使用聚合/組合的方式來(lái)實(shí)現(xiàn)。
說明:不得已使用繼承的話,必須符合里氏代換原則,此原則說父類能夠出現(xiàn)的地方子類一定能夠出現(xiàn),比如,“把錢交出來(lái)”,錢的子類美元、歐元、人民幣等都可以出現(xiàn)。
17.10、系統(tǒng)設(shè)計(jì)時(shí),根據(jù)依賴倒置原則,盡量依賴抽象類與接口,有利于擴(kuò)展與維護(hù)。
說明:低層次模塊依賴于高層次模塊的抽象,方便系統(tǒng)間的解耦。
17.11、系系統(tǒng)設(shè)計(jì)時(shí),注意對(duì)擴(kuò)展開放,對(duì)修改閉合。
說明:極端情況下,交付線上生產(chǎn)環(huán)境的代碼都是不可修改的,同一業(yè)務(wù)域內(nèi)的需求變化,通過模塊或類的擴(kuò)展來(lái)實(shí)現(xiàn)。
17.12、系統(tǒng)設(shè)計(jì)階段,共性業(yè)務(wù)或公共行為抽取出來(lái)公共模塊、公共配置、公共類、公共方法等,避免出現(xiàn)重復(fù)代碼或重復(fù)配置的情況。
說明:隨著代碼的重復(fù)次數(shù)不斷增加,維護(hù)成本指數(shù)級(jí)上升。
17.13、避免如下誤解:敏捷開發(fā) = 講故事 + 編碼 + 發(fā)布。
說明:敏捷開發(fā)是快速交付迭代可用的系統(tǒng),省略多余的設(shè)計(jì)方案,摒棄傳統(tǒng)的審批流程,但核心關(guān)鍵點(diǎn)上的必要設(shè)計(jì)和文檔沉淀是需要的。
反例:某團(tuán)隊(duì)為了業(yè)務(wù)快速發(fā)展,敏捷成了產(chǎn)品經(jīng)理催進(jìn)度的借口,系統(tǒng)中均是勉強(qiáng)能運(yùn)行但像面條一樣的代碼,可維護(hù)性和可擴(kuò)展性極差,一年之后,不得不進(jìn)行大規(guī)模重構(gòu),得不償失。
17.14、系統(tǒng)設(shè)計(jì)主要目的是明確需求、理順邏輯、后期維護(hù),次要目的用于指導(dǎo)編碼。
說明:避免為了設(shè)計(jì)而設(shè)計(jì),系統(tǒng)設(shè)計(jì)文檔有助于后期的系統(tǒng)維護(hù)和重構(gòu),所以設(shè)計(jì)結(jié)果需要進(jìn)行分類歸檔保存。
17.15、設(shè)計(jì)的本質(zhì)就是識(shí)別和表達(dá)系統(tǒng)難點(diǎn),找到系統(tǒng)的變化點(diǎn),并隔離變化點(diǎn)。
說明:世間眾多設(shè)計(jì)模式目的是相同的,即隔離系統(tǒng)變化點(diǎn)。
17.16、系統(tǒng)架構(gòu)設(shè)計(jì)的目的:
確定系統(tǒng)邊界。確定系統(tǒng)在技術(shù)層面上的做與不做。
確定系統(tǒng)內(nèi)模塊之間的關(guān)系。確定模塊之間的依賴關(guān)系及模塊的宏觀輸入與輸出。
確定指導(dǎo)后續(xù)設(shè)計(jì)與演化的原則。使后續(xù)的子系統(tǒng)或模塊設(shè)計(jì)在規(guī)定的框架內(nèi)繼續(xù)演化。
確定非功能性需求。非功能性需求是指安全性、可用性、可擴(kuò)展性等。
17.17、在做無(wú)障礙產(chǎn)品設(shè)計(jì)時(shí),需要考慮到:
所有可交互的控件元素必須能被 tab 鍵聚焦,并且焦點(diǎn)順序需符合自然操作邏輯。
用于登陸校驗(yàn)和請(qǐng)求攔截的驗(yàn)證碼均需提供圖形驗(yàn)證以外的其它方式。
自定義的控件類型需明確交互方式。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-458556.html
17.18、專有名詞解釋
POJO(Plain Ordinary Java Object): 在本手冊(cè)中,POJO 專指只有 setter / getter / toString 的簡(jiǎn)單類,包括 DO/DTO/BO/VO 等。
GAV(GroupId、ArtifactctId、Version): Maven 坐標(biāo),是用來(lái)唯一標(biāo)識(shí) jar 包。
OOP(Object Oriented Programming): 本手冊(cè)泛指類、對(duì)象的編程處理方式。
ORM(Object Relation Mapping): 對(duì)象關(guān)系映射,對(duì)象領(lǐng)域模型與底層數(shù)據(jù)之間的轉(zhuǎn)換,本文泛指 iBATIS, mybatis 等框架。
NPE(java.lang.NullPointerException): 空指針異常。
SOA(Service-Oriented Architecture): 面向服務(wù)架構(gòu),它可以根據(jù)需求通過網(wǎng)絡(luò)對(duì)松散耦合的粗粒度應(yīng)用組件進(jìn)行分布式部署、組合和使用,有利于提升組件可重用性,可維護(hù)性。
IDE(Integrated Development Environment): 用于提供程序開發(fā)環(huán)境的應(yīng)用程序,一般包括代碼編輯器、編譯器、調(diào)試器和圖形用戶界面等工具,本《手冊(cè)》泛指 IntelliJ IDEA 和 eclipse。
OOM(Out Of Memory): 源于 java.lang.OutOfMemoryError,當(dāng) JVM 沒有足夠的內(nèi)存來(lái)為對(duì)象分配空間并且垃圾回收器也無(wú)法回收空間時(shí),系統(tǒng)出現(xiàn)的嚴(yán)重狀況。
一方庫(kù):本工程內(nèi)部子項(xiàng)目模塊依賴的庫(kù)(jar 包)。
二方庫(kù):公司內(nèi)部發(fā)布到中央倉(cāng)庫(kù),可供公司內(nèi)部其它應(yīng)用依賴的庫(kù)(jar 包)。
三方庫(kù):公司之外的開源庫(kù)(jar 包)。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-458556.html
到了這里,關(guān)于阿里巴巴_java開發(fā)規(guī)范手冊(cè)詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!