新增11條新規(guī)約!阿里Java開發(fā)手冊|黃山版,擁抱規(guī)范,遠(yuǎn)離傷害

前言

阿里開發(fā)手冊是阿里近萬名開發(fā)同學(xué)集體智慧的結(jié)晶,以開發(fā)視角為中心,詳細(xì)列舉如何開發(fā)更加高效、更加容錯(cuò)、更加有協(xié)作性,力求知其然,更知其不然,結(jié)合正反例,讓Java開發(fā)者能夠提升協(xié)作效率、提高代碼質(zhì)量。

image

碼出高效,碼出質(zhì)量!

  • 你是否曾因Java代碼規(guī)范版本紛雜而無所適從?
  • 你是否想過代碼規(guī)范能將系統(tǒng)故障率降低20%?
  • 你是否曾因團(tuán)隊(duì)代碼風(fēng)格迥異而協(xié)同困難?
  • 你是否正在review一些原本可以避免的故障?
  • 你是否無法確定自己的代碼足夠健壯?
  • 阿里開發(fā)手冊(黃山版)

一、編程規(guī)約

(一) 命名風(fēng)格

1.【強(qiáng)制】所有編程相關(guān)的命名均不能以下劃線或美元符號開始,也不能以下劃線或美元符號結(jié)束。

  • 反例:_name / __name / Object / name\_ / name / Object$

2.【強(qiáng)制】所有編程相關(guān)的命名嚴(yán)禁使用拼音與英文混合的方式,更不允許直接使用中文的方式。

  • 說明:正確的英文拼寫和語法可以讓閱讀者易于理解,避免歧義。注意,即使純拼音命名方式也要避免采用。
  • 正例:ali / alibaba / taobao / kaikeba / aliyun / youku / hangzhou 等國際通用的名稱,可視同英文。
  • 反例:DaZhePromotion【打折】/ getPingfenByName()【評分】 / String fw【福娃】/ int 變量名 = 3

3.【強(qiáng)制】代碼和注釋中都要避免使用任何人類語言中的種族歧視性或侮辱性詞語。

  • 正例:blockList / allowList / secondary
  • 反例:blackList / whiteList / slave / SB / WTF

4.【強(qiáng)制】類名使用 UpperCamelCase 風(fēng)格,以下情形例外:DO / PO / DTO / BO / VO / UID 等。

  • 正例:ForceCode / UserDO / HtmlDTO / XmlService / TcpUdpDeal / TaPromotion
  • 反例:forcecode / UserDo / HTMLDto / XMLService / TCPUDPDeal / TAPromotion

5.【強(qiáng)制】方法名、參數(shù)名、成員變量、局部變量都統(tǒng)一使用 lowerCamelCase 風(fēng)格。

  • 正例:localValue / getHttpMessage() / inputUserId

6.【強(qiáng)制】常量命名應(yīng)該全部大寫,單詞間用下劃線隔開,力求語義表達(dá)完整清楚,不要嫌名字長。

  • 正例:MAX_STOCK_COUNT / CACHE_EXPIRED_TIME
  • 反例:MAX_COUNT / EXPIRED_TIME

7.【強(qiáng)制】抽象類命名使用 Abstract 或 Base 開頭;異常類命名使用 Exception 結(jié)尾,測試類命名以它要

  • 測試的類的名稱開始,以 Test 結(jié)尾。

8.【強(qiáng)制】類型與中括號緊挨相連來定義數(shù)組。

  • 正例:定義整形數(shù)組 int[] arrayDemo。
  • 反例:在 main 參數(shù)中,使用 String args[] 來定義。

9.【強(qiáng)制】POJO 類中的任何布爾類型的變量,都不要加 is 前綴,否則部分框架解析會引起序列化錯(cuò)誤。

  • 說明:本文 MySQL 規(guī)約中的建表約定第 1 條,表達(dá)是與否的變量采用 is_xxx 的命名方式,所以需要在<resultMap>設(shè)置從 is_xxx 到 xxx 的映射關(guān)系。
  • 反例:定義為基本數(shù)據(jù)類型 Boolean isDeleted 的屬性,它的方法也是 isDeleted(),框架在反向解析時(shí),“誤以為”對應(yīng)的屬性名稱是 deleted,導(dǎo)致屬性獲取不到,進(jìn)而拋出異常。

10.【強(qiáng)制】包名統(tǒng)一使用小寫,點(diǎn)分隔符之間有且僅有一個(gè)自然語義的英語單詞。包名統(tǒng)一使用單數(shù)形式,但是類名如果有復(fù)數(shù)含義,類名可以使用復(fù)數(shù)形式。

  • 正例:應(yīng)用工具類包名為 com.alibaba.ei.kunlun.aap.util;類名為 MessageUtils(此規(guī)則參考 spring 的框架結(jié)構(gòu))。

11.【強(qiáng)制】避免在子父類的成員變量之間、或者不同代碼塊的局部變量之間采用完全相同的命名,使可理解性降低。

  • 說明:子類、父類成員變量名相同,即使是 public 也是能夠通過編譯,而局部變量在同一方法內(nèi)的不同代碼塊中同名也是合法的,但是要避免使用。對于非 setter / getter 的參數(shù)名稱也要避免與成員變量名稱相同。
  • 反例:
public class ConfusingName {
protected int stock;
protected String alibaba;
// 非 setter/getter 的參數(shù)名稱,不允許與本類成員變量同名
public void access(String alibaba) {
if (condition) {
final int money = 666;
// ...
}
for (int i = 0; i < 10; i++) {
// 在同一方法體中,不允許與其它代碼塊中的 money 命名相同
final int money = 15978;
// ...
}
}
}
class Son extends ConfusingName {
// 不允許與父類的成員變量名稱相同
private int stock;
}

12.【強(qiáng)制】杜絕完全不規(guī)范的英文縮寫,避免望文不知義。

  • 反例:AbstractClass“縮寫”成 AbsClass;condition“縮寫”成 condi;Function“縮寫”成 Fu,此類隨意縮寫嚴(yán)重降低了代碼的可閱讀性。

13.【推薦】為了達(dá)到代碼自解釋的目標(biāo),任何自定義編程元素在命名時(shí),使用完整的單詞組合來表達(dá)。

  • 正例:在 JDK 中,對某個(gè)對象引用的 volatile 字段進(jìn)行原子更新的類名為 AtomicReferenceFieldUpdater。
  • 反例:常見的方法內(nèi)變量為 int a; 的定義方式。

14.【推薦】在常量與變量命名時(shí),表示類型的名詞放在詞尾,以提升辨識度。

  • 正例:startTime / workQueue / nameList / TERMINATED_THREAD_COUNT
  • 反例:startedAt / QueueOfWork / listName / COUNT_TERMINATED_THREAD

15.【推薦】如果模塊、接口、類、方法使用了設(shè)計(jì)模式,在命名時(shí)要體現(xiàn)出具體模式。

  • 說明:將設(shè)計(jì)模式體現(xiàn)在名字中,有利于閱讀者快速理解架構(gòu)設(shè)計(jì)思想。
  • 正例:
public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;

16.【推薦】接口類中的方法和屬性不要加任何修飾符號(public 也不要加),保持代碼的簡潔性,并加上有效的 Javadoc 注釋。盡量不要在接口里定義常量,如果一定要定義,最好確定該常量與接口的方法相關(guān),并且是整個(gè)應(yīng)用的基礎(chǔ)常量。

  • 正例:接口方法簽名 void commit();接口基礎(chǔ)常量 String COMPANY = "alibaba";
  • 反例:接口方法定義 public abstract void commit();
  • 說明:JDK8 中接口允許有默認(rèn)實(shí)現(xiàn),那么這個(gè) default 方法,是對所有實(shí)現(xiàn)類都有價(jià)值的默認(rèn)實(shí)現(xiàn)。

17.接口和實(shí)現(xiàn)類的命名有兩套規(guī)則:

1)【強(qiáng)制】對于 Service 和 DAO 類,基于 SOA 的理念,暴露出來的服務(wù)一定是接口,內(nèi)部的實(shí)現(xiàn)類用 Impl 的后綴與接口區(qū)別。

  • 正例:CacheServiceImpl 實(shí)現(xiàn) CacheService 接口。

2)【推薦】如果是形容能力的接口名稱,取對應(yīng)的形容詞為接口名(通常是 –able 結(jié)尾的形容詞)。

  • 正例:AbstractTranslator 實(shí)現(xiàn) Translatable。

18.【參考】枚舉類名帶上 Enum 后綴,枚舉成員名稱需要全大寫,單詞間用下劃線隔開。

  • 說明:枚舉其實(shí)就是特殊的常量類,且構(gòu)造方法被默認(rèn)強(qiáng)制是私有。
  • 正例:枚舉名字為 ProcessStatusEnum 的成員名稱:SUCCESS / UNKNOWN_REASON

19.【參考】各層命名規(guī)約:

A)Service / DAO 層方法命名規(guī)約:

  • 1)獲取單個(gè)對象的方法用 get 做前綴。
  • 2)獲取多個(gè)對象的方法用 list 做前綴,復(fù)數(shù)結(jié)尾,如:listObjects
  • 3)獲取統(tǒng)計(jì)值的方法用 count 做前綴。
  • 4)插入的方法用 save / insert 做前綴。
  • 5)刪除的方法用 remove / delete 做前綴。
  • 6)修改的方法用 update 做前綴。

B)領(lǐng)域模型命名規(guī)約:

  • 1)數(shù)據(jù)對象:xxxDO,xxx 即為數(shù)據(jù)表名。
  • 2)數(shù)據(jù)傳輸對象:xxxDTO,xxx 為業(yè)務(wù)領(lǐng)域相關(guān)的名稱。
  • 3)展示對象:xxxVO,xxx 一般為網(wǎng)頁名稱。
  • 4)POJO 是 DO / DTO / BO / VO 的統(tǒng)稱,禁止命名成 xxxPOJO。
image

(二) 常量定義

1.【強(qiáng)制】不允許任何魔法值(即未經(jīng)預(yù)先定義的常量)直接出現(xiàn)在代碼中。

反例:

// 開發(fā)者 A 定義了緩存的 key。
String key = "Id#taobao_" + tradeId;
cache.put(key, value);
// 開發(fā)者 B 使用緩存時(shí)直接復(fù)制少了下劃線,即 key 是"Id#taobao" + tradeId,導(dǎo)致出現(xiàn)故障。
String key = "Id#taobao" + tradeId;
cache.get(key);

2.【強(qiáng)制】long 或 Long 賦值時(shí),數(shù)值后使用大寫 L,不能是小寫 l,小寫容易跟數(shù)字混淆,造成誤解。

  • 說明:public static final Long NUM = 2l; 寫的是數(shù)字的 21,還是 Long 型的 2?

3.【強(qiáng)制】浮點(diǎn)數(shù)類型的數(shù)值后綴統(tǒng)一為大寫的 D 或 F。

正例:

public static final double HEIGHT = 175.5D;
 public static final float WEIGHT = 150.3F;

4.【推薦】不要使用一個(gè)常量類維護(hù)所有常量,要按常量功能進(jìn)行歸類,分開維護(hù)。

  • 說明:大而全的常量類,雜亂無章,使用查找功能才能定位到要修改的常量,不利于理解,也不利于維護(hù)。
  • 正例:緩存相關(guān)常量放在類 CacheConsts 下;系統(tǒng)配置相關(guān)常量放在類 SystemConfigConsts 下。

5.【推薦】常量的復(fù)用層次有五層:跨應(yīng)用共享常量、應(yīng)用內(nèi)共享常量、子工程內(nèi)共享常量、包內(nèi)共享常量、類內(nèi)共享常量。

  • 1)跨應(yīng)用共享常量:放置在二方庫中,通常是 client.jar 中的 constant 目錄下。
  • 2)應(yīng)用內(nèi)共享常量:放置在一方庫中,通常是子模塊中的 constant 目錄下。

反例:易懂常量也要統(tǒng)一定義成應(yīng)用內(nèi)共享常量,兩個(gè)程序員在兩個(gè)類中分別定義了表示“是”的常量:

類 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)致線上問題。

  • 3)子工程內(nèi)部共享常量:即在當(dāng)前子工程的 constant 目錄下。
  • 4)包內(nèi)共享常量:即在當(dāng)前包下單獨(dú)的 constant 目錄下。
  • 5)類內(nèi)共享常量:直接在類內(nèi)部 private static final 定義。

6.【推薦】如果變量值僅在一個(gè)固定范圍內(nèi)變化用 enum 類型來定義。

說明:如果存在名稱之外的延伸屬性應(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;
}
}

(三) 代碼格式

9.【強(qiáng)制】方法參數(shù)在定義和傳入時(shí),多個(gè)參數(shù)逗號后面必須加空格。

  • 正例:下例中實(shí)參的 args1 逗號后邊必須要有一個(gè)空格。
method(args1, args2, args3);

10.【強(qiáng)制】IDE 的 text file encoding 設(shè)置為 UTF-8;IDE 中文件的換行符使用 Unix 格式,不要使用Windows 格式。

11.【推薦】單個(gè)方法的總行數(shù)不超過 80 行。

  • 說明:除注釋之外的方法簽名、左右大括號、方法內(nèi)代碼、空行、回車及任何不可見字符的總行數(shù)不超過 80 行。
  • 正例:代碼邏輯分清紅花和綠葉,個(gè)性和共性,綠葉邏輯單獨(dú)出來成為額外方法,使主干代碼更加晰;共性邏輯抽取成為共性方法,便于復(fù)用和維護(hù)。

12.【推薦】沒有必要增加若干空格來使變量的賦值等號與上一行對應(yīng)位置的等號對齊。

正例:

int one = 1;
long two = 2L;
float three = 3F;
StringBuilder builder = new StringBuilder();
  • 說明:增加 builder 這個(gè)變量,如果需要對齊,則給 one、two、three 都要增加幾個(gè)空格,在變量比較多的情況下,是非常累贅的事情。

13.【推薦】不同邏輯、不同語義、不同業(yè)務(wù)的代碼之間插入一個(gè)空行,分隔開來以提升可讀性。

  • 說明:任何情形,沒有必要插入多個(gè)空行進(jìn)行隔開。

(四) OOP規(guī)約

23.【推薦】循環(huán)體內(nèi),字符串的連接方式,使用 StringBuilder 的 append 方法進(jìn)行擴(kuò)展。

反例:

String str = "start";
for (int i = 0; i < 100; i++) {
str = str + "hello";
}
  • 說明:反編譯出的字節(jié)碼文件顯示每次循環(huán)都會 new 出一個(gè) StringBuilder 對象,然后進(jìn)行 append 操作,最后通過toString() 返回 String 對象,造成內(nèi)存資源浪費(fèi)。

24.【推薦】final 可以聲明類、成員變量、方法、以及本地變量,下列情況使用 final 關(guān)鍵字:

  • 1)不允許被繼承的類,如:String 類。
  • 2)不允許修改引用的域?qū)ο?,如:POJO 類的域變量。
  • 3)不允許被覆寫的方法,如:POJO 類的 setter 方法。
  • 4)不允許運(yùn)行過程中重新賦值的局部變量。
  • 5)避免上下文重復(fù)使用一個(gè)變量,使用 final 關(guān)鍵字可以強(qiáng)制重新定義一個(gè)變量,方便更好地進(jìn)行重構(gòu)。

25.【推薦】慎用 Object 的 clone 方法來拷貝對象。

  • 說明:對象 clone 方法默認(rèn)是淺拷貝,若想實(shí)現(xiàn)深拷貝需覆寫 clone 方法實(shí)現(xiàn)域?qū)ο蟮纳疃缺闅v式拷貝。

26.【推薦】類成員與方法訪問控制從嚴(yán):

  • 1)如果不允許外部直接通過 new 來創(chuàng)建對象,那么構(gòu)造方法必須是 private。
  • 2)工具類不允許有 public 或 default 構(gòu)造方法。
  • 3)類非 static 成員變量并且與子類共享,必須是 protected。
  • 4)類非 static 成員變量并且僅在本類使用,必須是 private。
  • 5)類 static 成員變量如果僅在本類使用,必須是 private。
  • 6)若是 static 成員變量,考慮是否為 final。
  • 7)類成員方法只供類內(nèi)部調(diào)用,必須是 private。
  • 8)類成員方法只對繼承類公開,那么限制為 protected。

說明:任何類、方法、參數(shù)、變量,嚴(yán)控訪問范圍。過于寬泛的訪問范圍,不利于模塊解耦。思考:如果是一個(gè)private 的方法,想刪除就刪除,可是一個(gè) public 的 service 成員方法或成員變量,刪除一下,不得手心冒點(diǎn)汗嗎?變量像自己的小孩,盡量在自己的視線內(nèi),變量作用域太大,無限制的到處跑,那么你會擔(dān)心的。

(五) 日期時(shí)間

1.【強(qiáng)制】日期格式化時(shí),傳入 pattern 中表示年份統(tǒng)一使用小寫的 y。

  • 說明:日期格式化時(shí),yyyy 表示當(dāng)天所在的年,而大寫的 YYYY 代表是 week in which year(JDK7 之后引入的概念),意思是當(dāng)天所在的周屬于的年份,一周從周日開始,周六結(jié)束,只要本周跨年,返回的 YYYY 就是下一年。
  • 正例:表示日期和時(shí)間的格式如下所示:
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
  • 反例:某程序員因使用 YYYY/MM/dd 進(jìn)行日期格式化,2017/12/31 執(zhí)行結(jié)果為 2018/12/31,造成線上故障。

2.【強(qiáng)制】在日期格式中分清楚大寫的 M 和小寫的 m,大寫的 H 和小寫的 h 分別指代的意義。

  • 說明:日期格式中的這兩對字母表意如下:
  • 1)表示月份是大寫的 M
  • 2)表示分鐘則是小寫的 m
  • 3)24 小時(shí)制的是大寫的 H
  • 4)12 小時(shí)制的則是小寫的 h

3.【強(qiáng)制】獲取當(dāng)前毫秒數(shù):System.currentTimeMillis();而不是 new Date().getTime()。

  • 說明:獲取納秒級時(shí)間,則使用 System.nanoTime 的方式。在 JDK8 中,針對統(tǒng)計(jì)時(shí)間等場景,推薦使用 Instant 類。

4.【強(qiáng)制】不允許在程序任何地方中使用:1)java.sql.Date 2)java.sql.Time 3)java.sql.Timestamp。

  • 說明:第 1 個(gè)不記錄時(shí)間,getHours() 拋出異常;第 2 個(gè)不記錄日期,getYear() 拋出異常;第 3 個(gè)在構(gòu)造方法
super((time / 1000) * 1000),在 Timestamp 屬性 fastTime 和 nanos 分別存儲秒和納秒信息。
  • 反例:java.util.Date.after(Date) 進(jìn)行時(shí)間比較時(shí),當(dāng)入?yún)⑹?java.sql.Timestamp 時(shí),會觸發(fā) JDK BUG(JDK9 已修復(fù)),可能導(dǎo)致比較時(shí)的意外結(jié)果。

5.【強(qiáng)制】禁止在程序中寫死一年為 365 天,避免在公歷閏年時(shí)出現(xiàn)日期轉(zhuǎn)換錯(cuò)誤或程序邏輯錯(cuò)誤。

正例:

// 獲取今年的天數(shù)
int daysOfThisYear = LocalDate.now().lengthOfYear();
// 獲取指定某年的天數(shù)
LocalDate.of(2011, 1, 1).lengthOfYear();

反例:

// 第一種情況:在閏年 366 天時(shí),出現(xiàn)數(shù)組越界異常
int[] dayArray = new int[365];
// 第二種情況:一年有效期的會員制,2020 年 1 月 26 日注冊,硬編碼 365 返回的卻是 2021 年 1 月 25 日
Calendar calendar = Calendar.getInstance();
calendar.set(2020, 1, 26);
calendar.add(Calendar.DATE, 365);

6.【推薦】避免公歷閏年 2 月問題。閏年的 2 月份有 29 天,一年后的那一天不可能是 2 月 29 日。

7.【推薦】使用枚舉值來指代月份。如果使用數(shù)字,注意 Date,Calendar 等日期相關(guān)類的月份 month 取值范圍從 0 到 11 之間。

  • 說明:參考 JDK 原生注釋,Month value is 0-based. e.g., 0 for January.
  • 正例:Calendar.JANUARY,Calendar.FEBRUARY,Calendar.MARCH 等來指代相應(yīng)月份來進(jìn)行傳參或比較

(六) 集合處理

15.【強(qiáng)制】在 JDK7 版本及以上,Comparator 實(shí)現(xiàn)類要滿足如下三個(gè)條件,不然 Arrays.sort,Collections.sort 會拋 IllegalArgumentException 異常。

說明:三個(gè)條件如下

  • 1)x,y 的比較結(jié)果和 y,x 的比較結(jié)果相反。
  • 2)x > y,y > z,則 x > z。
  • 3)x = y,則 x,z 比較結(jié)果和 y,z 比較結(jié)果相同。

反例:下例中沒有處理相等的情況,交換兩個(gè)對象判斷結(jié)果并不互反,不符合第一個(gè)條件,在實(shí)際使用中可能會出現(xiàn)異常。

new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getId() > o2.getId() ? 1 : -1;
}
};

16.【推薦】泛型集合使用時(shí),在 JDK7 及以上,使用 diamond 語法或全省略。

說明:菱形泛型,即 diamond,直接使用<>來指代前邊已經(jīng)指定的類型。

正例:

// diamond 方式,即<>
HashMap<String, String> userCache = new HashMap<>(16);
// 全省略方式
ArrayList<User> users = new ArrayList(10);

17.【推薦】集合初始化時(shí),指定集合初始值大小。

  • 說明:HashMap 使用構(gòu)造方法 HashMap(int initialCapacity) 進(jìn)行初始化時(shí),如果暫時(shí)無法確定集合大小,那么指定默認(rèn)值(16)即可。
  • 正例:initialCapacity = (需要存儲的元素個(gè)數(shù) / 負(fù)載因子) + 1。注意負(fù)載因子(即 loaderfactor)默認(rèn)為 0.75,如果暫時(shí)無法確定初始值大小,請?jiān)O(shè)置為 16(即默認(rèn)值)。
  • 反例:HashMap 需要放置 1024 個(gè)元素,由于沒有設(shè)置容量初始大小,隨著元素增加而被迫不斷擴(kuò)容,resize() 方法總共會調(diào)用 8 次,反復(fù)重建哈希表和數(shù)據(jù)遷移。當(dāng)放置的集合元素個(gè)數(shù)達(dá)千萬級時(shí)會影響程序性能。

18.【推薦】使用 entrySet 遍歷 Map 類集合 KV,而不是 keySet 方式進(jìn)行遍歷。

  • 說明:keySet 其實(shí)是遍歷了 2 次,一次是轉(zhuǎn)為 Iterator 對象,另一次是從 hashMap 中取出 key 所對應(yīng)的 value。而只是遍歷了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用 Map.forEach 方法。
  • 正例:values() 返回的是 V 值集合,是一個(gè) list 集合對象;keySet() 返回的是 K 值集合,是一個(gè) Set 集合對象;entrySet() 返回的是 K-V 值組合的 Set 集合。

(七) 并發(fā)處理

13.【推薦】資金相關(guān)的金融敏感信息,使用悲觀鎖策略。

  • 說明:樂觀鎖在獲得鎖的同時(shí)已經(jīng)完成了更新操作,校驗(yàn)邏輯容易出現(xiàn)漏洞,另外,樂觀鎖對沖突的解決策略有較復(fù)雜的要求,處理不當(dāng)容易造成系統(tǒng)壓力或數(shù)據(jù)異常,所以資金相關(guān)的金融敏感信息不建議使用樂觀鎖更新。
  • 正例:悲觀鎖遵循一鎖二判三更新四釋放的原則。

14.【推薦】使用 CountDownLatch 進(jìn)行異步轉(zhuǎn)同步操作,每個(gè)線程退出前必須調(diào)用 countDown 方法,線程執(zhí)行代碼注意 catch 異常,確保 countDown 方法被執(zhí)行到,避免主線程無法執(zhí)行至 await 方法,直到超時(shí)才返回結(jié)果。

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

15.【推薦】避免 Random 實(shí)例被多線程使用,雖然共享該實(shí)例是線程安全的,但會因競爭同一 seed 導(dǎo)致的性能下降。

  • 說明:Random 實(shí)例包括 java.util.Random 的實(shí)例或者 Math.random() 的方式。
  • 正例:在 JDK7 之后,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前,需要編碼保證每個(gè)線程持有一個(gè)單獨(dú)的 Random 實(shí)例。

16.【推薦】通過雙重檢查鎖(double-checked locking),實(shí)現(xiàn)延遲初始化需要將目標(biāo)屬性聲明為volatile 型,(比如修改 helper 的屬性聲明為 private volatile Helper helper = null;)。

正例:

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

17.【參考】volatile 解決多線程內(nèi)存不可見問題對于一寫多讀,是可以解決變量同步問題,但是如果多寫,同樣無法解決線程安全問題。

說明:如果是 count++操作,使用如下類實(shí)現(xiàn):

AtomicInteger count = new AtomicInteger();
count.addAndGet(1);

如果是 JDK8,推薦使用 LongAdder 對象,比 AtomicLong 性能更好(減少樂觀鎖的重試次數(shù))。

18.【參考】HashMap 在容量不夠進(jìn)行 resize 時(shí)由于高并發(fā)可能出現(xiàn)死鏈,導(dǎo)致 CPU 飆升,在開發(fā)過程中注意規(guī)避此風(fēng)險(xiǎn)。

19.【參考】ThreadLocal 對象使用 static 修飾,ThreadLocal 無法解決共享對象的更新問題。

  • 說明:這個(gè)變量是針對一個(gè)線程內(nèi)所有操作共享的,所以設(shè)置為靜態(tài)變量,所有此類實(shí)例共享此靜態(tài)變量,也就是說在
  • 類第一次被使用時(shí)裝載,只分配一塊存儲空間,所有此類的對象(只要是這個(gè)線程內(nèi)定義的)都可以操控這個(gè)變量。

(八) 控制語句

10.【推薦】循環(huán)體中的語句要考量性能,以下操作盡量移至循環(huán)體外處理,如定義對象、變量、獲取數(shù)據(jù)庫連接,進(jìn)行不必要的 try-catch 操作(這個(gè) try-catch 是否可以移至循環(huán)體外)。

11.【推薦】避免采用取反邏輯運(yùn)算符。

  • 說明:取反邏輯不利于快速理解,并且取反邏輯寫法一般都存在對應(yīng)的正向邏輯寫法。
  • 正例:使用 if(x < 628) 來表達(dá) x 小于 628。
  • 反例:使用 if(!(x >= 628)) 來表達(dá) x 小于 628。

12.【推薦】公開接口需要進(jìn)行入?yún)⒈Wo(hù),尤其是批量操作的接口。

  • 反例:某業(yè)務(wù)系統(tǒng),提供一個(gè)用戶批量查詢的接口,API 文檔上有說最多查多少個(gè),但接口實(shí)現(xiàn)上沒做任何保護(hù),導(dǎo)致調(diào)用方傳了一個(gè) 1000 的用戶 id 數(shù)組過來后,查詢信息后,內(nèi)存爆了。

13.【參考】下列情形,需要進(jìn)行參數(shù)校驗(yàn):

  • 1)調(diào)用頻次低的方法。
  • 2)執(zhí)行時(shí)間開銷很大的方法。此情形中,參數(shù)校驗(yàn)時(shí)間幾乎可以忽略不計(jì),但如果因?yàn)閰?shù)錯(cuò)誤導(dǎo)致中間執(zhí)行回
  • 退,或者錯(cuò)誤,那得不償失。
  • 3)需要極高穩(wěn)定性和可用性的方法。
  • 4)對外提供的開放接口,不管是 RPC / API / HTTP 接口。
  • 5)敏感權(quán)限入口。

14.【參考】下列情形,不需要進(jìn)行參數(shù)校驗(yàn):

  • 1)極有可能被循環(huán)調(diào)用的方法。但在方法說明里必須注明外部參數(shù)檢查。
  • 2)底層調(diào)用頻度比較高的方法。畢竟是像純凈水過濾的最后一道,參數(shù)錯(cuò)誤不太可能到底層才會暴露問題。一般 DAO層與 Service 層都在同一個(gè)應(yīng)用中,部署在同一臺服務(wù)器中,所以 DAO 的參數(shù)校驗(yàn),可以省略。
  • 3)被聲明成 private 只會被自己代碼所調(diào)用的方法,如果能夠確定調(diào)用方法的代碼傳入?yún)?shù)已經(jīng)做過檢查或者肯定不會有問題,此時(shí)可以不校驗(yàn)參數(shù)。

(九) 注釋規(guī)約

10.【參考】對于注釋的要求:第一、能夠準(zhǔn)確反映設(shè)計(jì)思想和代碼邏輯;第二、能夠描述業(yè)務(wù)含義,使別的程序員能夠迅速了解到代碼背后的信息。完全沒有注釋的大段代碼對于閱讀者形同天書,注釋是給自己看的,即使隔很長時(shí)間,也能清晰理解當(dāng)時(shí)的思路;注釋也是給繼任者看的,使其能夠快速接替自己的工作。

11.【參考】好的命名、代碼結(jié)構(gòu)是自解釋的,注釋力求精簡準(zhǔn)確、表達(dá)到位。避免出現(xiàn)注釋的另一個(gè)極端:過多過濫的注釋,代碼的邏輯一旦修改,修改注釋又是相當(dāng)大的負(fù)擔(dān)。

反例:

// put elephant into fridge
put(elephant, fridge);

方法名 put,加上兩個(gè)有意義的變量名稱 elephant 和 fridge,已經(jīng)說明了這是在干什么,語義清晰的代碼不需要額外的注釋。

12.【參考】特殊注釋標(biāo)記,請注明標(biāo)記人與標(biāo)記時(shí)間。注意及時(shí)處理這些標(biāo)記,通過標(biāo)記掃描,經(jīng)常清理此類標(biāo)記。線上故障有時(shí)候就是來源于這些標(biāo)記處的代碼。

  • 1)待辦事宜(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)簽)。
  • 2)錯(cuò)誤,不能工作(FIXME):(標(biāo)記人,標(biāo)記時(shí)間,[預(yù)計(jì)處理時(shí)間])在注釋中用 FIXME 標(biāo)記某代碼是錯(cuò)誤的,而且不能工作,需要及時(shí)糾正的情況。

二、異常日志

(一) 錯(cuò)誤碼

10.【參考】錯(cuò)誤碼分為一級宏觀錯(cuò)誤碼、二級宏觀錯(cuò)誤碼、三級宏觀錯(cuò)誤碼。

  • 說明:在無法更加具體確定的錯(cuò)誤場景中,可以直接使用一級宏觀錯(cuò)誤碼,分別是:A0001(用戶端錯(cuò)誤)、B0001(系統(tǒng)執(zhí)行出錯(cuò))、C0001(調(diào)用第三方服務(wù)出錯(cuò))。
  • 正例:調(diào)用第三方服務(wù)出錯(cuò)是一級,中間件錯(cuò)誤是二級,消息服務(wù)出錯(cuò)是三級。

11.【參考】錯(cuò)誤碼的后三位編號與 HTTP 狀態(tài)碼沒有任何關(guān)系。

12.【參考】錯(cuò)誤碼有利于不同文化背景的開發(fā)者進(jìn)行交流與代碼協(xié)作。

說明:英文單詞形式的錯(cuò)誤碼不利于非英語母語國家(如阿拉伯語、希伯來語、俄羅斯語等)之間的開發(fā)者互相協(xié)作。

13.【參考】錯(cuò)誤碼即人性,感性認(rèn)知+口口相傳,使用純數(shù)字來進(jìn)行錯(cuò)誤碼編排不利于感性記憶和分類。

  • 說明:數(shù)字是一個(gè)整體,每位數(shù)字的地位和含義是相同的。
  • 反例:一個(gè)五位數(shù)字 12345,第 1 位是錯(cuò)誤等級,第 2 位是錯(cuò)誤來源,345 是編號,人的大腦不會主動地拆開并分辨每位數(shù)字的不同含義。

(二) 異常處理

11.【推薦】防止 NPE,是程序員的基本修養(yǎng),注意 NPE 產(chǎn)生的場景:

  • 1)返回類型為基本數(shù)據(jù)類型,return 包裝數(shù)據(jù)類型的對象時(shí),自動拆箱有可能產(chǎn)生 NPE
  • 反例:public int method() { return Integer 對象; },如果為 null,自動解箱拋 NPE。
  • 2)數(shù)據(jù)庫的查詢結(jié)果可能為 null。
  • 3)集合里的元素即使 isNotEmpty,取出的數(shù)據(jù)元素也可能為 null。
  • 4)遠(yuǎn)程調(diào)用返回對象時(shí),一律要求進(jìn)行空指針判斷,防止 NPE。
  • 5)對于 Session 中獲取的數(shù)據(jù),建議進(jìn)行 NPE 檢查,避免空指針。
  • 6)級聯(lián)調(diào)用 obj.getA().getB().getC();一連串調(diào)用,易產(chǎn)生 NPE。

正例:使用 JDK8 的 Optional 類來防止 NPE 問題。

12.【推薦】定義時(shí)區(qū)分 unchecked / checked 異常,避免直接拋出 new RuntimeException(),更不允許拋出 Exception 或者 Throwable,應(yīng)使用有業(yè)務(wù)含義的自定義異常。推薦業(yè)界已定義過的自定義異常,如:DAOException / ServiceException 等。

13.【參考】對于公司外的 http / api 開放接口必須使用錯(cuò)誤碼,而應(yīng)用內(nèi)部推薦異常拋出;跨應(yīng)用間RPC 調(diào)用優(yōu)先考慮使用 Result 方式,封裝 isSuccess() 方法、錯(cuò)誤碼、錯(cuò)誤簡短信息;應(yīng)用內(nèi)部推薦異常拋出。

說明:關(guān)于 RPC 方法返回方式使用 Result 方式的理由:

1)使用拋異常返回方式,調(diào)用方如果沒有捕獲到就會產(chǎn)生運(yùn)行時(shí)錯(cuò)誤。

2)如果不加棧信息,只是 new 自定義異常,加入自己的理解的 error message,對于調(diào)用端解決問題的幫助不會太多。

如果加了棧信息,在頻繁調(diào)用出錯(cuò)的情況下,數(shù)據(jù)序列化和傳輸?shù)男阅軗p耗也是問題。

三、單元測試

12.【推薦】對于不可測的代碼在適當(dāng)?shù)臅r(shí)機(jī)做必要的重構(gòu),使代碼變得可測避免為了達(dá)到測試要求而書寫不規(guī)范測試代碼。

13.【推薦】在設(shè)計(jì)評審階段,開發(fā)人員需要和測試人員一起確定單元測試范圍,單元測試最好覆蓋所有測試用例(UC)。

14.【推薦】單元測試作為一種質(zhì)量保障手段,在項(xiàng)目提測前完成單元測試,不建議項(xiàng)目發(fā)布后補(bǔ)充單元測試用例。

15.【參考】為了更方便地進(jìn)行單元測試,業(yè)務(wù)代碼應(yīng)避免以下情況:

  • 構(gòu)造方法中做的事情過多。
  • 存在過多的全局變量和靜態(tài)方法。
  • 存在過多的外部依賴。
  • 存在過多的條件語句。

說明:多層條件語句建議使用衛(wèi)語句、策略模式、狀態(tài)模式等方式重構(gòu)。

16.【參考】不要對單元測試存在如下誤解:

  • 那是測試同學(xué)干的事情。本文是開發(fā)手冊,凡是本文內(nèi)容都是與開發(fā)同學(xué)強(qiáng)相關(guān)的。
  • 單元測試代碼是多余的。系統(tǒng)的整體功能與各單元部件的測試正常與否是強(qiáng)相關(guān)的。
  • 單元測試代碼不需要維護(hù)。一年半載后,那么單元測試幾乎處于廢棄狀態(tài)。
  • 單元測試與線上故障沒有辯證關(guān)系。好的單元測試能夠最大限度地規(guī)避線上故障。

四、安全規(guī)約

8.【強(qiáng)制】在使用平臺資源,譬如短信、郵件、電話、下單、支付,必須實(shí)現(xiàn)正確的防重放的機(jī)制,如數(shù)量限制、疲勞度控制、驗(yàn)證碼校驗(yàn),避免被濫刷而導(dǎo)致資損。

說明:如注冊時(shí)發(fā)送驗(yàn)證碼到手機(jī),如果沒有限制次數(shù)和頻率,那么可以利用此功能騷擾到其它用戶,并造成短信平臺資源浪費(fèi)。

9.【強(qiáng)制】對于文件上傳功能,需要對于文件大小、類型進(jìn)行嚴(yán)格檢查和控制。

說明:攻擊者可以利用上傳漏洞,上傳惡意文件到服務(wù)器,并且遠(yuǎn)程執(zhí)行,達(dá)到控制網(wǎng)站服務(wù)器的目的。

10.【強(qiáng)制】配置文件中的密碼需要加密。

11.【推薦】發(fā)貼、評論、發(fā)送等即時(shí)消息,需要用戶輸入內(nèi)容的場景。必須實(shí)現(xiàn)防刷、內(nèi)容違禁詞過濾等風(fēng)控策略。

五、MySQL數(shù)據(jù)庫

(一) 建表規(guī)約

15.【推薦】單表行數(shù)超過 500 萬行或者單表容量超過 2GB,才推薦進(jìn)行分庫分表。

說明:如果預(yù)計(jì)三年后的數(shù)據(jù)量根本達(dá)不到這個(gè)級別,請不要在創(chuàng)建表時(shí)就分庫分表。

16.【參考】合適的字符存儲長度,不但節(jié)約數(shù)據(jù)庫表空間、節(jié)約索引存儲,更重要的是提升檢索速度。

正例:無符號值可以避免誤存負(fù)數(shù),且擴(kuò)大了表示范圍:

(二) 索引規(guī)約

9.【推薦】建組合索引的時(shí)候,區(qū)分度最高的在最左邊。

  • 正例:如果 where a = ? and b = ?,a 列的幾乎接近于唯一值,那么只需要單建 idx_a 索引即可。
  • 說明:存在非等號和等號混合判斷條件時(shí),在建索引時(shí),請把等號條件的列前置。如:where c > ? and d = ? 那么即使c 的區(qū)分度更高,也必須把 d 放在索引的最前列,即建立組合索引 idx_d_c。

10.【推薦】防止因字段類型不同造成的隱式轉(zhuǎn)換,導(dǎo)致索引失效。

11.【參考】創(chuàng)建索引時(shí)避免有如下極端誤解:

  • 1)索引寧濫勿缺。認(rèn)為一個(gè)查詢就需要建一個(gè)索引。
  • 2)吝嗇索引的創(chuàng)建。認(rèn)為索引會消耗空間、嚴(yán)重拖慢記錄的更新以及行的新增速度。
  • 3)抵制唯一索引。認(rèn)為唯一索引一律需要在應(yīng)用層通過“先查后插”方式解決。

(三) SQL語句

11.【推薦】in 操作能避免則避免,若實(shí)在避免不了,需要仔細(xì)評估 in 后邊的集合元素?cái)?shù)量,控制在1000 個(gè)之內(nèi)。

12.【參考】因國際化需要,所有的字符存儲與表示,均采用 utf8 字符集,那么字符計(jì)數(shù)方法需要注意。

說明:

SELECT LENGTH("輕松工作");--返回為 12
SELECT CHARACTER_LENGTH("輕松工作");--返回為 4

如果需要存儲表情,那么選擇 utf8mb4 來進(jìn)行存儲,注意它與 utf8 編碼的區(qū)別。

13.【參考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系統(tǒng)和事務(wù)日志資源少,但 TRUNCATE無事務(wù)且不觸發(fā) trigger,有可能造成事故,故不建議在開發(fā)代碼中使用此語句。

說明:TRUNCATE TABLE 在功能上與不帶 WHERE 子句的 DELETE 語句相同。

(四) ORM映射

7.【強(qiáng)制】更新數(shù)據(jù)表記錄時(shí),必須同時(shí)更新記錄對應(yīng)的 update_time 字段值為當(dāng)前時(shí)間。

8.【推薦】不要寫一個(gè)大而全的數(shù)據(jù)更新接口。傳入為 POJO 類,不管是不是自己的目標(biāo)更新字段,都進(jìn)行update table set c1 = value1 , c2 = value2 , c3 = value3;這是不對的。執(zhí)行 SQL 時(shí),不要更新無改動的字段,一是易出錯(cuò);二是效率低;三是增加 binlog 存儲。

9.【參考】@Transactional 事務(wù)不要濫用。事務(wù)會影響數(shù)據(jù)庫的 QPS,另外使用事務(wù)的地方需要考慮各方面的回滾方案,包括緩存回滾、搜索引擎回滾、消息補(bǔ)償、統(tǒng)計(jì)修正等。

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

六、工程結(jié)構(gòu)

(一) 應(yīng)用分層

1.【推薦】根據(jù)業(yè)務(wù)架構(gòu)實(shí)踐,結(jié)合業(yè)界分層規(guī)范與流行技術(shù)框架分析,推薦分層結(jié)構(gòu)如圖所示,默認(rèn)上層依賴于下層,箭頭關(guān)系表示可直接依賴,如:開放 API 層可以依賴于 Web 層(Controller 層),也可以直接依賴于 Service 層,依此類推:

image

(二) 二方庫依賴

11.【推薦】二方庫不要有配置項(xiàng),最低限度不要再增加配置項(xiàng)。

12.【推薦】不要使用不穩(wěn)定的工具包或者 Utils 類。

  • 說明:不穩(wěn)定指的是提供方無法做到向下兼容,在編譯階段正常,但在運(yùn)行時(shí)產(chǎn)生異常,因此,盡量使用業(yè)界穩(wěn)定的二方工具包。

13.【參考】為避免應(yīng)用二方庫的依賴沖突問題,二方庫發(fā)布者應(yīng)當(dāng)遵循以下原則:

  • 1)精簡可控原則.移除一切不必要的 API 和依賴,只包含 Service API、必要的領(lǐng)域模型對象、Utils 類、常量、枚舉等。如果依賴其它二方庫,盡量是 provided 引入,讓二方庫使用者去依賴具體版本號;無 log 具體實(shí)現(xiàn),只依賴日志框架。
  • 2)稅定可追潮原則.每個(gè)版本的變化應(yīng)該被記錄,二方庫由誰維護(hù),源碼在哪里,都需要能方便查到。除非用戶主動升級版本,否則公共二方庫的行為不應(yīng)該發(fā)生變化。

(三) 服務(wù)器

6.【推薦】在線上生產(chǎn)環(huán)境,JVM 的 Xms 和 Xmx 設(shè)置一樣大小的內(nèi)存容量,避免在 GC 后調(diào)整堆大小帶來的壓力。

7.【推薦】了解每個(gè)服務(wù)大致的平均耗時(shí),可以通過獨(dú)立配置線程池,將較慢的服務(wù)與主線程池隔離開,免得不同服務(wù)的線程同歸于盡。

8.【參考】服務(wù)器內(nèi)部重定向必須使用 forward;外部部重定向地址必須使用 URL Broker 生成,否則因線上采用 HTTPS 協(xié)議而導(dǎo)致瀏覽器提示“不安全”。此外,還會帶來 URL 維護(hù)不一致的問題。

七、設(shè)計(jì)規(guī)約

ps:新增11條新規(guī)約!

例如,浮點(diǎn)數(shù)的后綴統(tǒng)一為大寫;枚舉的屬性字段必須是私有且不可變;配置文件中的密碼需要加密等。

新增描述中的正反例 2 條

例如,多個(gè)構(gòu)造方法次序、NoSuchMethodError 處理;

新增擴(kuò)展說明 5 條

例如,父集合元素的增加或刪除異常等。

修改描述 22 處

例如,魔法值的示例代碼、ScheduledThreadPool 問題等。

修正嵩山版中部分代碼格式錯(cuò)誤和描述錯(cuò)誤。

image

2022阿里最新版的Java開發(fā)手冊(黃山版)已經(jīng)整理好了,開發(fā)的同學(xué)們趕緊行動起來,遵守代碼規(guī)范,你好,我好,大家好!

無規(guī)矩不成方圓 無規(guī)范不能協(xié)作

眾所周知,制訂交通法規(guī)表面上是要限制行車權(quán),實(shí)際上是保障公眾的人身安全。試想如果沒有限速,沒有紅綠燈,沒有規(guī)定靠右行駛,誰還敢上路行駛。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,514評論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,373評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,975評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,743評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,199評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,414評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,951評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,780評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,218評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,649評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,889評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,673評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內(nèi)容