Go 代碼整潔之道

痛點:

  1. 工程剛開始非常整潔倍靡,隨著時間的流逝,逐漸變得不太好維護了..
  2. 多人開發(fā)同一工程時课舍,架構(gòu)層次不清晰塌西,重復造輪子?
  3. 接手了一個舊工程筝尾,如何快速理解架構(gòu)與設(shè)計捡需,從而快速上手做需求?

有規(guī)范的好處:

  1. 利于多人合作開發(fā)&理解同一模塊/工程筹淫。
  2. 降低團隊成員之間的代碼溝通成本站辉。
  3. 架構(gòu)&代碼規(guī)范明確,有效提高編碼效率损姜。

前言:

讀這本書的時饰剥,第一個想到的問題就是:“什么是整潔的代碼?”
書中列舉了各位程序員鼻祖的名言摧阅,我整理總結(jié)了下汰蓉,大概有下面幾條:

  • 邏輯直截了當,令缺陷難以隱藏 棒卷。
  • 減少依賴關(guān)系顾孽,便于維護祝钢。
  • 合理分層,完善錯誤處理 若厚。
  • 只做好一件事太颤。沒有重復代碼。

代碼是團隊溝通的一種方式

工作的溝通盹沈,不只是每天lark拉群或者開會交流,代碼也是我們很重要的溝通方式之一吃谣。

用代碼寫完需求乞封,只是萬里長征的第一步。我們要用代碼表達自己的設(shè)計思想岗憋。如果我們團隊大部分人都能按照一定規(guī)范肃晚、思路去寫代碼。那么仔戈,工作溝通成本會降低許多关串。
比如:某位同學之前負責的一個模塊,被另一位同事接手了监徘,或者隨著業(yè)務的擴張晋修,我們多個同學共同開發(fā)同一個工程/模塊。如果我們的代碼結(jié)構(gòu)大同小異凰盔,分層清晰墓卦、注釋合理,就會降低很多溝通成本户敬。

因此落剪,我們需要為團隊創(chuàng)造整潔的代碼。

一是降低團隊內(nèi)的代碼溝通成本尿庐,二是便于今后項目需求的維護與迭代忠怖。

讓營地比來時更整潔

隨著需求的不斷迭代,保持代碼整潔抄瑟、工程更易理解凡泣。

有時候,我們會維護一些老項目锐借,或者交接過來的項目问麸。代碼可能不太美觀,工程可能不太好理解钞翔。

一般我們會面臨兩種選擇:

  1. 重構(gòu)
  2. 優(yōu)化迭代

重構(gòu)的成本比較高严卖,得先理解原有邏輯,再進行重新設(shè)計落地布轿。代價大哮笆,周期長来颤,短期看不到效果。

在人力有限的情況下稠肘。我們一般會先選擇“優(yōu)化迭代”福铅。

這時候,我們每做一個新需求 / 修復一個bug時项阴,我們要盡可能的去小范圍“重構(gòu)”滑黔。

每一次Merge,代碼都比之前更干凈环揽,工程變得更好理解略荡。那么,我們的工程就不會變的更糟歉胶。

清理不一定要花多少功夫汛兜。也許只是改一個更加容易理解的命名;抽象一個函數(shù)通今,消除一點重復/冗余代碼粥谬;處理一下嵌套的 if / else 等等。

一辫塌、有意義的命名

名副其實:
起有意義的名字漏策,讓人一目了然。
一看這個變量璃氢,就能知道它存儲的是什么對象哟玷。
一看這個方法,就能知道它處理的是什么事一也。
一看這個包名巢寡,就能知道它負責處理哪個模塊。

看看反例:

var array []int64
var theList []int64
var num int64

看看正例:

var mrList []*MRInfo
var buildNum int64

避免誤導:
不要用太長或者很偏僻的單詞來命名椰苟,也不要用拼音代替英文抑月。
更不要用容易混淆的字母(字母+數(shù)字)。尤其是lO兩個字母舆蝴,和數(shù)字1和0太像了谦絮。

看看反例:

func getDiZhi() string {
   // ..
}

func modifyPassword(password1, password2 string) string {
   // ..
}

看看正例:

func getAddress() string {
   // ..
}

func modifyPassword(oldPassword, newPassword string) string {
   // ..
}

有意義的區(qū)分:
聲明兩個同類型的變量/函數(shù),需要用有明確意義的命名加以區(qū)分洁仗。

看看反例:

var accountData []*Account
var account []*Account

func Account(id int) *Account {
    // ...
}

func AccountData(id int) *Account {
    // ...
}

可讀可搜索:
起可讀的层皱,可以被搜索的名字。

看看反例:

var ymdhms = "2021-08-04 01:55:55"
var a = 1

看看正例:

var date = "2021-08-04 01:55:55"
var buildNum = 1

命名規(guī)范(重點)

package

  • 同一項目下赠潦,不允許出現(xiàn)同名的package叫胖。
  • 只由小寫字母組成。不包含大寫字母和下劃線等字符她奥。
  • 簡短并包含一定的上下文信息瓮增。例如time怎棱、http等。
  • 不能是含義模糊的常用名绷跑,或者與標準庫同名拳恋。例如不能使用util或者strings
  • 包名能夠作為路徑的 base name砸捏,在一些必要的時候谬运,需要把不同功能拆分為子包。(例如應該使用encoding/base64而不是encoding_base64或者encodingbase64垦藏。)

以下規(guī)則按照先后順序盡量滿足

  • 不使用常用變量名作為包名吩谦。
  • 使用單數(shù)而不是復數(shù)。(關(guān)鍵字除外膝藕,例如consts
  • 謹慎地使用縮寫,保證理解咐扭。

文件名

  • 文件名都使用小寫字母芭挽,且使用單數(shù)形式,如需要可使用下劃線分割蝗肪。

函數(shù)和方法

Function 的命名應該遵循如下原則:

  • 對于可導出的函數(shù)使用大寫字母開頭袜爪,對于內(nèi)部使用的函數(shù)使用小寫字母開頭。
  • 若函數(shù)或方法為判斷類型(返回值主要為 bool 類型)薛闪,則名稱應以 has, is, can 等判斷性動詞開頭辛馆。
// HasPrefix tests whether the string s begins with prefix.
func HasPrefix(s, prefix string) bool {...
  • 函數(shù)采用駝峰命名,不能使用下劃線豁延,不能重復包名前綴昙篙。例如使用http.Server而不是http.HTTPServer,因為包名和函數(shù)名總是成對出現(xiàn)的诱咏。
// WriteRune appends the UTF-8 encoding of Unicode code point r to b's buffer.
// It returns the length of r and a nil error.
func (b *Builder) WriteRune(r rune) (int, error) {...
  • 遵守簡單的原則苔可,不應該像 ToString 這類的方法名,而直接使用 String 代替袋狞。
// String returns the accumulated string.
func (b *Builder) String() string {...
  • Receiver 要盡量簡短并有意義
    • 不要使用面向?qū)ο缶幊讨械某S妹俑ā@绮灰褂?code>self、this苟鸯、me等同蜻。
    • 一般使用 1 到 2 個字母的縮寫代表其原來的類型。例如類型為Client早处,可以使用c湾蔓、cl等。
    • 在每個此類型的方法中使用統(tǒng)一的縮寫陕赃。例如在其中一個方法中使用了c代表了Client卵蛉,在其他的方法中也要使用c而不能使用諸如cl的命名颁股。
func (r *Reader) Len() int {...

常量

  • 常量使用駝峰形式。(盡量不要用下劃線)
const AppVersion = "1.1.1"
  • 如果是枚舉類型的常量傻丝,需要先創(chuàng)建相應類型:
type Scheme string 

 const ( 
    HTTP  Scheme = "http" 
    HTTPS Scheme = "https" 
 )

變量

  • 變量命名基本上遵循相應的英文表達或簡寫甘有。
  • 采用駝峰命名,不能使用下劃線葡缰。首字母是否大寫根據(jù)是否需要外部訪問來定亏掀。
  • 遇到專有名詞時,可以不改變原來的寫法泛释。例如:
{ 
    "API":   true, 
    "ASCII": true, 
    "CPU":   true, 
    "CSS":   true, 
    "DNS":   true, 
    "EOF":   true, 
    "GUID":  true, 
    "HTML":  true, 
    "HTTP":  true, 
    "HTTPS": true, 
    "ID":    true, 
    "IP":    true, 
    "JSON":  true, 
    "LHS":   true, 
    "QPS":   true, 
    "RAM":   true, 
    "RHS":   true, 
    "RPC":   true, 
    "SLA":   true, 
    "SMTP":  true, 
    "SSH":   true, 
    "TLS":   true, 
    "TTL":   true, 
    "UI":    true, 
    "UID":   true, 
    "UUID":  true, 
    "URI":   true, 
    "URL":   true, 
    "UTF8":  true, 
    "VM":    true, 
    "XML":   true, 
    "XSRF":  true, 
    "XSS":   true, 
}

二滤愕、函數(shù)

短小

盡可能的縮短每個函數(shù)的長度。能抽象就抽象怜校。
任何一個函數(shù)都不應該超過50行间影。甚至,20行封頂最佳茄茁。(PS:16寸mac滿屏是60多行)
想象下魂贬,如果有個幾百行,甚至上千行的函數(shù)裙顽。后面維護得多困難付燥。

單參數(shù)

每個函數(shù)最理想應該是有0或1個入?yún)ⅰ?br> 盡量不要超過三個入?yún)ⅰH绻^愈犹,建議封裝成結(jié)構(gòu)體键科。

只做一件事

函數(shù)應該只做一件事,做好這件事漩怎,只做這一件事勋颖。

抽象層級

按順序,自頂向下讀代碼/寫代碼勋锤。

看看反例:

// 更新組件升級結(jié)果
func UpdatePodUpgradeResult(ctx context.Context, req *UpdatePodReq) error {
   // 更新組件核心表牙言,寫了20行

   // 更新歷史,寫了40行

   // 更新構(gòu)建產(chǎn)物怪得,寫了20行

   // ...代碼越來越多咱枉,越來越不好維護。

   return nil
}

看看正例:

// 更新組件升級結(jié)果
func UpdatePodUpgradeResult(ctx context.Context, req *UpdatePodReq) error {
   // 更新組件
   err = updatePodMain(ctx, req)
   if err != nil {
      return err
   }

   // 更新歷史
   err = updatePodHistory(ctx, req)
   if err != nil {
      return err
   }

   // 更新Builds
   err = updatePodBuilds(ctx, req)
   if err != nil {
      return err
   }

   return nil
}

func updatePodMain(ctx context.Context, req *UpdatePodReq) error {
   // ...
}

func updatePodHistory(ctx context.Context, req *UpdatePodReq) error {
   // ...
}

func updatePodBuilds(ctx context.Context, req *UpdatePodReq) error {
   // ...
}

盡量少嵌套 if / else

看看反例:

func GetItem(extension string) (Item, error) {
    if refIface, ok := db.ReferenceCache.Get(extension); ok {
        if ref, ok := refIface.(string); ok {
            if itemIface, ok := db.ItemCache.Get(ref); ok {
                if item, ok := itemIface.(Item); ok {
                    if item.Active {
                        return Item, nil
                    } else {
                      return EmptyItem, errors.New("no active item found in cache")
                    }
                } else {
                  return EmptyItem, errors.New("could not cast cache interface to Item")
                }
            } else {
              return EmptyItem, errors.New("extension was not found in cache reference")
            }
        } else {
          return EmptyItem, errors.New("could not cast cache reference interface to Item")
        }
    }
    return EmptyItem, errors.New("reference not found in cache")
}

看看正例:

func GetItem(extension string) (Item, error) {
    refIface, ok := db.ReferenceCache.Get(extension)
    if !ok {
        return EmptyItem, errors.New("reference not found in cache")
    }

    ref, ok := refIface.(string)
    if !ok {
        // return cast error on reference 
    }

    itemIface, ok := db.ItemCache.Get(ref)
    if !ok {
        // return no item found in cache by reference
    }

    item, ok := itemIface.(Item)
    if !ok {
        // return cast error on item interface
    }

    if !item.Active {
        // return no item active
    }

    return Item, nil
}

安全并發(fā)處理(SafeGo)

建議:開協(xié)程的地方徒恋,盡量使用SafeGo(內(nèi)部有 recover 以及打印 panic 堆棧日志)

func SafeGo(ctx context.Context, f func()) {
   go func() {
      defer func() {
         if err := recover(); err != nil {
            content := fmt.Sprintf("Safe Go Capture Panic In Go Groutine\n%s", string(debug.Stack())){
               logs.CtxFatal(ctx, content)
            }
         }
      }()

      f()
   }()
}

For 循環(huán)并發(fā)處理(Routine Pool)

for 循環(huán)開協(xié)程時蚕断,優(yōu)先考慮使用封裝的 Routine Pool(協(xié)程池)控制并發(fā)量。

好處:

  1. 避免協(xié)程創(chuàng)建過多入挣,導致程序崩潰亿乳。(對服務本身)
  2. 控制流量速度,防止把下游服務打雪崩。(對下游服務)

參考代碼:

type content struct {
    work func() error
    end  *struct{}
}

func work(w func() error) content {
    return content{work: w}
}

func end() content {
    return content{end: &struct{}{}}
}

// Goroutine routine_pool
type RoutinePool struct {
    capacity uint
    ch       chan content
}

func NewRoutinePool(ctx context.Context, capacity uint) *RoutinePool {
    ch := make(chan content)
    pool := RoutinePool{
        capacity: capacity,
        ch:       ch,
    }

    for i := uint(0); i < capacity; i++ {
        SafeGo(ctx, func() {
            for {
                select {
                case cont := <-ch:
                    if cont.end != nil {
                        return
                    }

                    if cont.work != nil {
                        if err := cont.work(); err != nil {
                            LogCtxError(ctx, "run work failed: %v", err)
                        }
                    }
                }
            }
        })
    }

    return &pool
}

func (pool *RoutinePool) Submit(w func() error) {
    pool.ch <- work(w)
}

func (pool *RoutinePool) Shutdown() {
    defer close(pool.ch)
    for i := uint(0); i < pool.capacity; i++ {
        pool.ch <- end()
    }
}

Copy 傳入?yún)f(xié)程的 Context

Gin:直接調(diào)用context.Copy()即可葛假。

三障陶、注釋與格式

注釋

  • 所有可導出的函數(shù)、類型聊训、變量等都應該有注釋抱究,注釋以函數(shù)名、類型名带斑、變量名打頭鼓寺,函數(shù)注釋建議同時包含參數(shù)和返回值的說明。
  • 每行注釋不超過100個字符勋磕。
  • 包妈候、函數(shù)、方法和類型的注釋說明都是一個完整的句子挂滓。
  • 有具體方案文檔苦银,在對應地方留下文檔鏈接注釋。便于后續(xù)快速了解這部分需求赶站。

格式

這部分只要我們打開 Goland 相關(guān)配置墓毒,即可完成。

推薦配置

File Watcher 開啟 go fmt亲怠、go imports:

image

配置可以參考:https://www.jetbrains.com/help/go/using-file-watchers.html#enableFileWatcher

垂直格式:

每個文件從上到下的代碼規(guī)范。

一個文件柠辞,盡量不要超過 400 行团秽。(超過可讀性會降低)

  1. 垂直方向的間隔

package聲明、導入聲明和每個函數(shù)之間都要有一個空行隔開叭首。

  1. 垂直方向的靠近:

靠的越近的代碼习勤,關(guān)系越緊密。

  1. 垂直距離:

變量聲明:盡可能靠近其使用的位置焙格。
局部變量图毕,聲明在函數(shù)頂部。
實體變量眷唉,聲明在類的頂部予颤。

相關(guān)函數(shù):盡節(jié)能互相靠近,保證順序冬阳。

首先蛤虐,應該放到一起。
其次肝陪,“調(diào)用”函數(shù)應該放到“被調(diào)用”函數(shù)的上面驳庭。

概念相關(guān):做某類事情的函數(shù),應該放一起。

比如饲常,一個 interface蹲堂,它有 read/write 方法,他們應該放一起

  1. 垂直順序:

“調(diào)用”函數(shù)應該放到“被調(diào)用”函數(shù)的上面贝淤。
建立了一種自頂向下貫穿源代碼的良好信息流柒竞。

橫向格式:

每一行代碼從左到右的代碼規(guī)范。

每一行代碼霹娄,盡量不要超過 120 個字能犯。(超過150字,一個屏幕就看不全了)

  1. 水平方向的間隔與靠近

操作符周圍加上空格犬耻。

  1. 水平對齊
type PodType string

const (
   PodTypeIOS      PodType = "iOS"
   PodTypeAndroid  PodType = "Android"
   PodTypeFlutter  PodType = "Flutter"
)
  1. 縮進

這部分 go-fmt 幫我們做了踩晶,只要集成 go-fmt 即可。

四枕磁、對象與數(shù)據(jù)結(jié)構(gòu)

數(shù)據(jù)抽象成對象

以組件升級為例渡蜻,將組件升級流程抽象成對象。不關(guān)心底層的數(shù)據(jù)結(jié)構(gòu)與實現(xiàn)计济。

分析茸苇,組件升級流程需要:

  • ValidateParam(校驗參數(shù))
  • FormatParam(格式化參數(shù))
  • SendUpgradeRequest(觸發(fā)升級)
  • GenerateHistory(生成歷史)
  • UpdateHistory(更新歷史)
type mpaasRepoUpgradeHandlerType interface {
   ValidateParam(ctx context.Context) error                                                   //判斷某個升級請求,是否合法
   FormatUpgradeParam(ctx context.Context) error                                              //處理參數(shù)沦寂,補充額外信息或者補上默認信息等等
   SendUpgradeRequest(ctx context.Context, history *podHistory) (int, error) //各 Handler 自行發(fā)送升級請求
   UpgradeHistory(ctx context.Context) *podHistory                           //生成升級歷史
   UpdateHistoryInfo(ctx context.Context) *podHistory                        //重試的時候要更新的組件升級歷史字段
   baseHandler() *podUpgradeBaseHandler                                                 //獲取 baseHandler
}

組件升級會分為多種:iOS学密、AndroidFlutter传藏、Custom(構(gòu)建腳本)腻暮、RubyGem等等..

不論哪種組件升級只要實現(xiàn)這套 interface,即可完成組件升級流程毯侦。

數(shù)據(jù) vs. 對象

對象:把數(shù)據(jù)隱藏于抽象之后哭靖,暴露操作數(shù)據(jù)的方法。

數(shù)據(jù):通過數(shù)據(jù)結(jié)構(gòu)暴露處理侈离。

面向過程(直接使用數(shù)據(jù)結(jié)構(gòu)):
好處:在不改動既有數(shù)據(jù)結(jié)構(gòu)的前提下试幽,新增新函數(shù)。
壞處:難以增刪改數(shù)據(jù)結(jié)構(gòu)卦碾。

面向?qū)ο螅ǔ橄螅?br> 好處:方便增刪改數(shù)據(jù)結(jié)構(gòu)铺坞。
壞處:難以新增函數(shù),必須所有類改洲胖。

兩者沒有絕對的優(yōu)劣比較康震,需要 case by case 在具體場景下的應用。

得墨忒(tuī)耳律
模塊不應該了解它所操作對象的內(nèi)部結(jié)構(gòu)宾濒。
對象需要隱藏數(shù)據(jù)腿短,暴露操作。

五、錯誤處理

常規(guī)流程

  • 先看看反例:
package smelly
func (store *Store) GetItem(id string) (Item, error) {
    store.mtx.Lock()
    defer store.mtx.Unlock()

    item, ok := store.items[id]
    if !ok {
        return Item{}, errors.New("item could not be found in the store") 
    }
    return item, nil
}

handler里如果要對特殊錯誤做特殊處理:

func GetItemHandler(w http.ReponseWriter, r http.Request) {
    item, err := smelly.GetItem("123")
    if err != nil {
        if err.Error() == "item could not be found in the store" {
            http.Error(w, err.Error(), http.StatusNotFound)
                return
        }
        http.Error(w, errr.Error(), http.StatusInternalServerError)
        return
    } 
    json.NewEncoder(w).Encode(item)
}

  • 再看看正例:

提前在包里橘忱,定義好錯誤類型赴魁。

package clean

var (
    ErrItemNotFound = errors.New("item could not be found in the store") 
)

func (store *Store) GetItem(id string) (Item, error) {
    store.mtx.Lock()
    defer store.mtx.Unlock()

    item, ok := store.items[id]
    if !ok {
        return nil, ErrItemNotFound
    }
    return item, nil
}

handler里如果要對特殊錯誤做特殊處理:

func GetItemHandler(w http.ReponseWriter, r http.Request) {
    item, err := clean.GetItem("123")
    if err != nil {
        if errors.Is(err, clean.ErrItemNotFound) {
           http.Error(w, err.Error(), http.StatusNotFound)
                return
        }
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    } 
    json.NewEncoder(w).Encode(item)
}

好處:方便拓展,增加代碼可讀性钝诚。

六颖御、邊界

我們的系統(tǒng)都微服務化了。

每個子服務都會存在自己的邊界凝颇。

我們需要盡量保證我們的服務邊界整潔潘拱。

邊界整潔

我們依賴的服務、庫拧略、代碼是要可控的芦岂。
假如,我們依賴了一個不可控的庫垫蛆。
如果他有一天被檢測出有安全問題禽最、亦或 bug。
我們就很被動袱饭,導致服務需要大改川无。

簡單來說,依賴我們能控制的東西虑乖,好過依賴我們控制不了的東西懦趋。
免得日后被控制,導致重寫或修改疹味。

層級架構(gòu)明確

屬于同一層的服務仅叫,最好只依賴下層服務。
理論上來說佛猛,不該依賴同層服務,更不應該依賴上層服務坠狡。

每個團隊/業(yè)務的架構(gòu)圖應該要梳理出來继找。

模塊職責明確

其實,不光服務于服務之間要有層級架構(gòu)逃沿。
我們服務內(nèi)部應該也需要按照層級來寫代碼婴渡。
另外,每個工程的 ReadMe凯亮,最好能闡述下大概設(shè)計思路和架構(gòu)边臼,便于協(xié)作開發(fā)。

參考資料:

clean-go-article

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末假消,一起剝皮案震驚了整個濱河市柠并,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖臼予,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸣戴,死亡現(xiàn)場離奇詭異,居然都是意外死亡粘拾,警方通過查閱死者的電腦和手機窄锅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缰雇,“玉大人入偷,你說我怎么就攤上這事⌒涤矗” “怎么了疏之?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長戒良。 經(jīng)常有香客問我体捏,道長,這世上最難降的妖魔是什么糯崎? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任几缭,我火速辦了婚禮,結(jié)果婚禮上沃呢,老公的妹妹穿的比我還像新娘年栓。我一直安慰自己,他們只是感情好薄霜,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布某抓。 她就那樣靜靜地躺著,像睡著了一般惰瓜。 火紅的嫁衣襯著肌膚如雪否副。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天崎坊,我揣著相機與錄音备禀,去河邊找鬼。 笑死奈揍,一個胖子當著我的面吹牛曲尸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播男翰,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼另患,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蛾绎?” 一聲冷哼從身側(cè)響起昆箕,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤鸦列,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后为严,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體敛熬,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年第股,在試婚紗的時候發(fā)現(xiàn)自己被綠了应民。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡夕吻,死狀恐怖诲锹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情涉馅,我是刑警寧澤归园,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站稚矿,受9級特大地震影響庸诱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晤揣,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一桥爽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧昧识,春花似錦钠四、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至甸祭,卻和暖如春缕碎,著一層夾襖步出監(jiān)牢的瞬間角虫,已是汗流浹背炒瘟。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纵东,地道東北人煞檩。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓处嫌,卻偏偏與公主長得像栅贴,于是被迫代替她去往敵國和親斟湃。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351