淺談 golang 代碼規(guī)范, 性能優(yōu)化和需要注意的坑

淺談 golang 代碼規(guī)范, 性能優(yōu)化和需要注意的坑

編碼規(guī)范

[強制] 聲明slice

申明 slice 最好使用

var t []int 

而不是使用

t := make([]int, 0)

因為 var 并沒有初始化邓梅,但是 make 初始化了瞻凤。

但是如果要指定 slice 的長度或者 cap,可以使用 make

最小作用域

if err := DoSomething(); err != nil {
    return err
}

盡量減少作用域, GC 比較友好

賦值規(guī)范

聲明一個對象有4種方式:make, new(), var, :=

比如:

t := make([]int, 0)
u := new(User)
var t []int
u := &User{}
  • var 聲明但是不立刻初始化
  • := 聲明并立刻使用
  • 盡量減少使用 new() 因為他不會初始化值, 使用 u := User{} 更好

接口命名

單個功能使用 er 結(jié)尾或者名詞

type Reader interface {
    Read(p []byte) (n int, err error)
}

2 個功能

type ReaderWriter interface {
    Reader
    Writer
}

3 個及以上功能

type Car interface {
    Drive()
    Stop()
    Recover()
}

命名規(guī)范

代碼風(fēng)格

[強制] go 文件使用下劃線命名

[強制] 常量使用下劃線或者駝峰命名, 表達清除不要嫌名字太長

[推薦] 不要在名字中攜帶類型信息

// 反例
userMap := map[string]User{}
// 正例
users := map[string]User{}

[推薦] 方法的參數(shù)要能表達含義

// 反例
func CopyFile(a, b string) error 
// 正例
func CopyFile(src, dst string) error 

[推薦] 包名一律使用小寫字母, 不要加下劃線或者中劃線

[推薦] 如果使用了設(shè)計模式, 名稱中體現(xiàn)設(shè)計模式的含義

type AppFactory interface {
    CreateApp() App
}

[推薦] 如果變量名是 bool 類型, 如果字段名不能表達 bool 類型, 可以使用 is 或者 has 前綴

var isDpdk bool

[強制] 一個變量只能有一個功能, 并和名稱一致, 不要把一個變量作為多種用途

// 反例
length := len(userList)
length := len(orderList)
// 正例
userNum := len(userList)
orderNum := len(orderList)

[推薦] 如果變量名是 bool 類型, 如果字段名不能表達 bool 類型, 可以使用 is 或者 has 前綴

golang 基本規(guī)范

包設(shè)計

[強制] 包的設(shè)計滿足單一職責(zé)

說明: 在 SRP (Single Response Principle) 模式中, 單一職責(zé)原則是指一個類只負(fù)責(zé)一項功能, 并且不能負(fù)責(zé)多個功能. 將包設(shè)計的非常內(nèi)聚, 減少包之間的 api

[強制] 包的設(shè)計遵循最小可見性原則

說明: 僅在包內(nèi)調(diào)用的函數(shù), 或者變量, 或者結(jié)構(gòu)體, 或者接口, 或者類型, 或者函數(shù)等等, 需要小寫開頭, 不可以可以被外部包訪問

[強制] 代碼需要可測試性, 使用接口和依賴注入替代硬編碼

[強制] 單元測試文件放到代碼文件同級目錄, 便于 golang 工具使用

比如: vscode 在方法上右鍵可以直接生成測試代碼和測試覆蓋率并可視化展示執(zhí)行情況

布局

[推薦] 程序?qū)嶓w之間使用空行區(qū)分, 增加可讀性

說明: 比如函數(shù)中各個模塊功能使用空行區(qū)分, 增加可讀性

[推薦] 每個文件末尾應(yīng)該有且僅有一個空行

[推薦] 一元操作符不要加空格, 二元操作符的才需要

注釋

[推薦] 可導(dǎo)出的方法, 變量, 結(jié)構(gòu)體等都需要注釋

表達式和語句

[推薦] if 或者循環(huán)的嵌套層數(shù)不宜大于 3

[推薦] 對于 for 遍歷, 優(yōu)先使用 range 而不是顯式'的下標(biāo), 如果 value 占用內(nèi)存大的話可以使用顯式下標(biāo)

說明: range 可以讓代碼更加整潔, 特別是多層 for 嵌套的時候, 但是 range 非拷貝值, 如果 value 不是指針類型, 而且占用內(nèi)存較大會有性能損耗.

函數(shù)

[強制] 命名不要暴露實現(xiàn)細(xì)節(jié), 一般以"做什么"來命名而不是"怎么做"

[推薦] 短小精悍, 盡量控制到 20 行左右

說明: 函數(shù)的粒度越小, 可復(fù)用的概率越大, 而且函數(shù)越多, 高層函數(shù)調(diào)用起來就是代碼可讀性很高, 讀起來就像一系列解釋

[推薦] 單一職責(zé), 函數(shù)只做好一件事情, 只做一件事情

[強制] 不要設(shè)置多功能函數(shù)

例如: 一個函數(shù)既修改了狀態(tài), 又返回了狀態(tài), 應(yīng)該拆分

[推薦] 為簡單的功能編寫函數(shù)

說明: 為 1,2 行代碼編寫函數(shù)也是必要的, 增加代碼的可復(fù)用性, 增加高層函數(shù)的可讀性, 可維護性, 可測試性

參數(shù)

[推薦] 參數(shù)個數(shù)限制在 3 個以內(nèi), 如果超過了, 可以使用配置類或者 Options 設(shè)計模式

說明: 函數(shù)的最理想的參數(shù)的個數(shù)首先是0, 然后是 1, 然后是 2, 3 就會很差了. 因為參數(shù)有很多概念性, 可讀性差, 而且讓測試十分復(fù)雜

[推薦] 函數(shù)不能含有表示參數(shù)

說明: 標(biāo)識參數(shù)丑陋不堪, 函數(shù)往往根據(jù)標(biāo)識參數(shù)走不同的邏輯, 這個和單一職責(zé)違背

[強制] struct 作為參數(shù)傳遞的時候, 使用指針

說明: 函數(shù)的執(zhí)行就是壓棧, struct 如果有多個字段將會被多次壓棧, 有性能損失, 指針只會被壓棧一次

[推薦] 在 api(controller) 層對傳入的參數(shù)進行檢查, 而不是每一層都檢查一次

[推薦] 當(dāng) chan 作為函數(shù)的參數(shù)的時候, 根據(jù)最小權(quán)限原則, 使用單向 chan

// 讀取單向chan
func Parse(ch <-chan struct{}) {
    for v := range ch {
        println(v)
    }
}

// 寫入單向chan
func Do(down chan<- struct{}) {
    time.Sleep(time.Second)
    down <- struct{}{}
}

返回值

[推薦] 返回值的個數(shù)不要大于 3

[強制] 統(tǒng)一定義錯誤, 不要隨便拋出錯誤

說明: 比如記錄不存在可能有多種錯誤

"record not exits"
"record not exited"
"record not exited!!"

上層函數(shù)要處理底層的錯誤的話, 要知道所有的拋出情況, 這個是不現(xiàn)實的, 需要處理的錯誤應(yīng)該使用統(tǒng)一文件定義錯誤碼

[強制] 沒有失敗原因的時候, 不要使用 error

// 正例
func IsPhone() bool 
// 反例
func IsPhone() error

[推薦] 當(dāng)多重試幾次可以避免失敗的時候, 不要返回 error

錯誤是偶然發(fā)生的, 應(yīng)該給一個機會重試, 可以避免大多數(shù)的偶然問題

[推薦] 上層函數(shù)不關(guān)心 error 的時候, 不要返回 error

比如 Close(), Clear() 拋出了 error, 上層函數(shù)大概率不知道怎么處理

異常設(shè)計

[推薦] 程序的開發(fā)階段, 堅持速錯, 讓異常程序崩潰

說明: 速錯的本質(zhì)邏輯就是 "讓它掛", 只有掛了你才第一時間知道錯誤, panic 能讓 bug 盡快被修復(fù)

[強制] 程序部署后, 應(yīng)該避免終止

是否 recover 應(yīng)該根據(jù)配置文件確定, 默認(rèn)需要 recover

注意: 有時候需要在延遲函數(shù)中釋放資源, 比如 panic 之前 read 了 channel, 但是還沒有 write 就 panic , 需要在 deffer 函數(shù)中做好處理, 防止 channel 阻塞.

[推薦] 當(dāng)入?yún)⒉缓戏ǖ臅r候, panic

說明: 當(dāng)入?yún)⒉缓戏ǖ臅r候, panic, 可以讓上層函數(shù)知道錯誤, 而不是繼續(xù)執(zhí)行(api 應(yīng)該提前做好參數(shù)檢查)

整潔測試

[強制] 不要為了測試對代碼進行入侵式的修改, 應(yīng)該 mock

說明: 禁止為了測試在函數(shù)中增加條件分支和測試變量

[推薦] 測試的三要數(shù), 可讀性, 可讀性, 可讀性

生產(chǎn)代碼的可靠性由測試代碼來保證, 測試代碼的可靠性由最簡單的可讀性來保證, 邏輯需要簡單到?jīng)]有 bug

REFERENCE

bilibili go 規(guī)范

uber go-guide https://github.com/xxjwxc/uber_go_guide_cn

golang 性能優(yōu)化

內(nèi)存優(yōu)化

小對象合并

小對象在堆內(nèi)存上頻繁的創(chuàng)建和銷毀, 會導(dǎo)致內(nèi)存碎片, 一般會才使用內(nèi)存池

golang 的內(nèi)存機制也是內(nèi)存池, 每個 span 大小為 4KB, 同時維護一個 cache, cache 有一個 list 數(shù)組

數(shù)組里面儲存的是鏈表, 就像 HashMap 的拉鏈法, 數(shù)組的每個格子代表的內(nèi)存大小是不一樣的, 64 位的機器是 8 byte 為基礎(chǔ), 比如下標(biāo) 0 是 8 byte 大小的鏈表節(jié)點, 下標(biāo) 1 是 16 byte 的鏈表節(jié)點, 每個下標(biāo)的內(nèi)存不一樣, 使用的是按需分配最近的內(nèi)存, 比如一個結(jié)構(gòu)體的內(nèi)存實際上算下來是 31 byte, 分配的時候會分配 32 byte.

一個下標(biāo)的一條鏈表的每個 Node 儲存的內(nèi)存是一致的.

所以建議將小對象合并為一個 struct

for k, v := range m {
    x := struct {k , v string} {k, v} // copy for capturing by the goroutine
    go func() {
        // using x.k & x.v
    }()
}

使用 buf 緩存

協(xié)議編碼的時候需要頻繁的操作 buf, 可以使用 bytes.Buffer 作為緩存區(qū)對象, 它會一次性分配足夠大的內(nèi)存, 避免內(nèi)存不夠的時候動態(tài)申請內(nèi)存, 減少內(nèi)存分配次數(shù), 而且, buf 可以被復(fù)用(建議復(fù)用)

slice 和 map 創(chuàng)建的時候, 預(yù)估大小指定的容量

預(yù)先分配內(nèi)存, 可以減少動態(tài)擴容帶來的開銷

t := make([]int, 0, 100)
m := make(map[string]int, 100)

如果不確定 slice 會不會初始化, 使用 var 這樣不會分配內(nèi)存, make([]int,0) 會分配內(nèi)存空間

var t []int

拓展:

slice 容量在 1024 前擴容是倍增, 1024 后是1/4

map 的擴容機制比較復(fù)雜, 每次擴容是 2 倍數(shù), 結(jié)構(gòu)體中有一個 bucket 和 oldBuckets 實現(xiàn)增量擴容

長調(diào)用棧避免申請較多的臨時對象

說明: goroutine 默認(rèn)的 棧的大小是 4K, 1.7 改為 2K, 它采用的是連續(xù)棧的機制, 當(dāng)椞跆颍空間不夠的時候, goroutine 會不斷擴容, 每次擴容就先 slice 的擴容一樣, 設(shè)計新的棧空間申請和舊椝ィ空間的拷貝, 如果 GC 發(fā)現(xiàn)現(xiàn)在的空間只有之前的 1/4 又會縮容, 頻繁的內(nèi)存申請和拷貝會帶來開銷

建議: 控制函數(shù)調(diào)用棧幀的復(fù)雜度, 避免創(chuàng)建過多的臨時對象, 如果確實需要比較長的調(diào)用椑跗猓或者 job 類型的代碼, 可以考慮將 goroutine 池化

避免頻繁創(chuàng)建臨時變量

說明: GC STW 的時間已經(jīng)優(yōu)化到最糟糕 1ms 內(nèi)了, 但是還是有混合寫屏障會降低性能, 如果臨時變量個數(shù)太多, GC 性能損耗就高.

建議: 降低變量的作用域, 使用局部變量, 最小可見性, 將多個變量合并為一個 struct 數(shù)組(降低掃描次數(shù))

大的 struct 使用指針傳遞

golang 都是值拷貝, 特別是 struct 入棧幀的時候會將變量一個一個入棧, 頻繁申請內(nèi)存, 可以使用指針傳遞來優(yōu)化性能

并發(fā)優(yōu)化

goroutine 池化

go 雖然輕量, 但是對于高并發(fā)的輕量級任務(wù), 比如高并發(fā)的 job 類型的代碼, 可以考慮使用 goroutine 池化, 減少 goroutine 的創(chuàng)建和銷毀, 減少 goroutine 的創(chuàng)建和銷毀的開銷

減少系統(tǒng)調(diào)用

goroutine 的實現(xiàn)是通過同步模擬異步操作, 比如下面的操作并不會阻塞, runtime 的線程調(diào)度

  • 網(wǎng)絡(luò)IO
  • channel
  • time.Sleep
  • 基于底層異步的 SysCall

下面的阻塞會創(chuàng)建新的線程調(diào)度

  • 本地 IO
  • 基于底層同步的 SysCall
  • CGO 調(diào)用 IO 或者其他阻塞

建議將同步調(diào)用: 隔離到可控 goroutine 中, 而不是直接高并 goroutine 調(diào)用

減少鎖, 減少大鎖

Go 推薦使用 channel 的方式調(diào)用而不是共享內(nèi)存, channel 之間存在大鎖, 可以將鎖的力度降低

拓展: channel

channel 不要傳遞大數(shù)據(jù), 會有值拷貝

channel 的底層是鏈表 + 鎖

不要用 channel 傳遞圖片等數(shù)據(jù), 任何的隊列的性能都很低, 可以嘗試指針優(yōu)化大對象

合并請求 singleflight

參考: singleflight

協(xié)議壓縮 protobuf

protobuf 比 json 的儲存效率和解析效率更高, 推薦在持久化或者數(shù)據(jù)傳輸?shù)臅r候使用 protobuf 替代 json

批量協(xié)議

對數(shù)據(jù)訪問接口提供批量協(xié)議, 比如門面設(shè)計模式或者 pipeline, 可以減少非常多的 IO, QPS, 和拆包解包的開銷

并行請求 errgroup

對于網(wǎng)關(guān)接口, 通常需要聚合多個模塊的數(shù)據(jù), 當(dāng)這些業(yè)務(wù)模塊數(shù)據(jù)之間沒有依賴的時候, 可以并行請求, 減少耗時

ctxTimeout, cf := context.WithTimeout(context.Background(), time.Second)
defer cf()
g, ctx := errgroup.WithContext(ctxTimeout)
var urls = []string{
    "http://www.golang.org/",
    "http://www.google.com/",
    "http://www.somestupidname.com/",
}
for _, url := range urls {
    // Launch a goroutine to fetch the URL.
    url := url // https://golang.org/doc/faq#closures_and_goroutines
    g.Go(func() error {
        // Fetch the URL.
        resp, err := http.Get(url)
        if err == nil {
            resp.Body.Close()
        }
        return err
    })
}
// Wait for all HTTP fetches to complete.
if err := g.Wait(); err == nil {
    fmt.Println("Successfully fetched all URLs.")
}
select {
case <-ctx.Done():
    fmt.Println("Context canceled")
default:
    fmt.Println("Context not canceled")
}

其他優(yōu)化

需要注意的坑

channel 之坑

如何優(yōu)雅的關(guān)閉 channel

參考: 如何優(yōu)雅的關(guān)閉 channel

關(guān)閉 channel 的坑

  • 關(guān)閉已經(jīng)關(guān)閉的 channel 會導(dǎo)致 panic
  • 給關(guān)閉的 channel 發(fā)送數(shù)據(jù)會導(dǎo)致 panic
  • 從關(guān)閉的 channel 中讀取數(shù)據(jù)是初始值默認(rèn)值

CCP 原則

CCP: Channel Close Principle (關(guān)閉通道原則)

  • 不要從接收端關(guān)閉 channel
  • 不要關(guān)閉有多個發(fā)送端的 channel
  • 當(dāng)發(fā)送端只有一個且后面不會再發(fā)送數(shù)據(jù)才可以關(guān)閉 channel

有緩存的 channel 不一定有序

defer 之坑

defer 中的變量

參數(shù)傳遞是在調(diào)用的時候

i := 1
defer println("defer", i)
i++
// defer 1

非參數(shù)的閉包

i := 1
defer func() {
    println("defer", i)
}()
i++
// defer 2

有名返回同理閉包, 并且會修改有名返回的返回值

func main(){
    fmt.Printf("main: %v\n", getNum())
    // defer 2
    // main: 2
}

func getNum() (i int) {
    defer func() {
        i++
        println("defer", i)
    }()
    i++
    return
}

不要 for 循環(huán)中調(diào)用 deffer

因為 deffer 只會在函數(shù) return 之后執(zhí)行, 這樣會累積大量的 deffer 而且極其容易出錯

建議: 將 for 循環(huán)需要 deffer 的代碼邏輯封裝為一個函數(shù)

HTTP 之坑

request 超時時間

golang 的 http 默認(rèn)的 request 沒有超時, 這是一個大坑, 因為如果服務(wù)器沒有響應(yīng), 也沒有斷開, 客戶端會一直等待, 導(dǎo)致客戶端阻塞, 量一上來就崩潰了

關(guān)閉 HTTP 的 response

http 請求框架的 response 一定要通過 Close 方法關(guān)閉, 不然有可能內(nèi)存泄露

interface 之坑

interface 到底什么才等于 nil?

說明: interface{}和接口類型 不同于 struct, 接口底層有 2 個成員, 一個是 type 一個是 value, 只有當(dāng) type 和 value 都為 nil 時, interface{} 才等于 nil

var u interface{} = (*interface{})(nil)
if u == nil {
    t.Log("u is nil")
} else {
    t.Log("u is not nil")
}
// u is not nil

接口

var u Car = (Car)(nil)
if u == nil {
    t.Log("u is nil")
} else {
    t.Log("u is not nil")
}
// u is nil

自定義的 struct

var u *user = (*user)(nil)
if u == nil {
    t.Log("u is nil")
} else {
    t.Log("u is not nil")
}
// u is nil

map 之坑

map 并發(fā)讀寫

map 并發(fā)讀寫會 panic, 需要加鎖或者使用 sync.Map

map 不能直接更新 value 的某一個字段

type User struct{
    name string
}
func TestMap(t *testing.T) {
    m := make(map[string]User)
    m["1"] = User{name:"1"}
    m["1"].name = "2"
    // 編譯失敗,不能直接修改map的一個字段值
}

需要單獨拿出來

func TestMap(t *testing.T) {
    m := make(map[string]User)
    m["1"] = User{name: "1"}
    u1 := m["1"]
    u1.name = "2"
}

切片之坑

數(shù)組是值類型, 切片是引用類型(指針)

func TestArray(t *testing.T) {
    a := [1]int{}
    setArray(a)
    println(a[0])
    // 0
}
func setArray(a [1]int) {
    a[0] = 1
}
func TestSlice(t *testing.T) {
    a := []int{
        1,
    }
    setSlice(a)
    println(a[0])
    // 1
}
func setSlice(a []int) {
    a[0] = 1
}

range 遍歷

range 會給每一個元素創(chuàng)建一個副本, 會有值拷貝, 如果數(shù)組存的是大的結(jié)構(gòu)體可以用 index 遍歷或者指針優(yōu)化

因為 value 是副本, 所以不能修改原有的值

append 會改變地址

slice 類型的本質(zhì)是一個結(jié)構(gòu)體

type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

函數(shù)的值拷貝會導(dǎo)致修改失效

func TestAppend1(t *testing.T) {
    var a []int
    add(a)
    println(len(a))
    // 0
}

func add(a []int) {
    a = append(a, 1)
}

閉包之坑

并發(fā)下 go 函數(shù)閉包問題

for i := 0; i < 3; i++ {
    go func() {
        println(i)
    }()
}
time.Sleep(time.Second)
// 2
// 2
// 2

說明: 因為閉包導(dǎo)致 i 變量逃逸到堆空間, 所有的 go 共用了 i 變量, 導(dǎo)致并發(fā)問題

解決方法1: 局部變量

for i := 0; i < 3; i++ {
    ii := i
    go func() {
        println(ii)
    }()
}
time.Sleep(time.Second)
// 2
// 0
// 1

解決方法2: 參數(shù)傳遞

for i := 0; i < 3; i++ {
    go func(ii int) {
        println(ii)
    }(i)
}
time.Sleep(time.Second)
// 2
// 0
// 1

buffer 之坑

buffer 對象池

buffer 對象池一定要用完才還回去, 不然buffer在多處復(fù)用導(dǎo)致底層的 []byte 內(nèi)容不一致

參考: golang-buffer-pool

我們的一個 httpClient 返回處理使用了 sync.pool 緩存 buffer, 測試是內(nèi)存優(yōu)化了6-8倍

后面測試的時候發(fā)現(xiàn), 獲取的內(nèi)容會偶爾不一致, review 代碼發(fā)現(xiàn)可能是并發(fā)時候 buffer 指針放回去了還在使用, 導(dǎo)致和buffer pool 里面不一致
首先考慮就是將 buffer 的 bytes 讀取出來, 然后再 put 回池子里面
然后 bytes 是一個切片, 底層還是和 buffer 共用一個 []byte, buffer 再次修改的時候底層的 []byte 也會被修改, 導(dǎo)致狀態(tài)不一致
這些理論上是并發(fā)問題, 但是我們測試發(fā)現(xiàn), 單線程調(diào)用 httpClient 時候, 有時候會有問題, 有時候又沒有問題
官方的 http client 做請求的時候會開一個協(xié)程, sync pool在同一個協(xié)程下面復(fù)用對象是一致的, 但是多協(xié)程就會新建, 會嘗試通過協(xié)程的id獲取與之對應(yīng)的對象, 沒有才去新建.
串行執(zhí)行請求也會產(chǎn)生多個協(xié)程, 所以偶爾會觸發(fā)新建 sync 的buffer, 如果新建就不會報錯, 如果不新建就會報錯.

select 之坑

for select default 之坑

for 中的 default 在 select 一定會執(zhí)行, CPU 一直被占用不會讓出, 導(dǎo)致 CPU 空轉(zhuǎn)

示例代碼

func TestForSelect(t *testing.T) {
    for {
        select {
        case <-time.After(time.Second * 1):
            println("hello")
        default:
            if math.Pow10(100) == math.Pow(10, 100) {
                println("equal")
            }
        }
    }
}

top CPU 跑滿了

top - 15:00:50 up 1 day, 15:55,  0 users,  load average: 1.36, 0.85, 0.35
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND   
28632 root      20   0 2168296   1.4g   2244 S 252.8  11.7   1:04.15 __debug_bin   
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末呀伙,一起剝皮案震驚了整個濱河市补履,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌剿另,老刑警劉巖箫锤,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異雨女,居然都是意外死亡谚攒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門氛堕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來馏臭,“玉大人,你說我怎么就攤上這事岔擂。” “怎么了浪耘?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵乱灵,是天一觀的道長。 經(jīng)常有香客問我七冲,道長痛倚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任澜躺,我火速辦了婚禮蝉稳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘掘鄙。我一直安慰自己耘戚,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布操漠。 她就那樣靜靜地躺著收津,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上撞秋,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天长捧,我揣著相機與錄音,去河邊找鬼吻贿。 笑死串结,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的舅列。 我是一名探鬼主播肌割,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼剧蹂!你這毒婦竟也來了声功?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤宠叼,失蹤者是張志新(化名)和其女友劉穎先巴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冒冬,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡伸蚯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了简烤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剂邮。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖横侦,靈堂內(nèi)的尸體忽然破棺而出挥萌,到底是詐尸還是另有隱情,我是刑警寧澤枉侧,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布引瀑,位于F島的核電站,受9級特大地震影響榨馁,放射性物質(zhì)發(fā)生泄漏憨栽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一翼虫、第九天 我趴在偏房一處隱蔽的房頂上張望屑柔。 院中可真熱鬧,春花似錦珍剑、人聲如沸掸宛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽旁涤。三九已至翔曲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間劈愚,已是汗流浹背瞳遍。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留菌羽,地道東北人掠械。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像注祖,于是被迫代替她去往敵國和親猾蒂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,654評論 2 354

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