Swift 官方API設計指南(翻譯)

原文鏈接:https://swift.org/documentation/api-design-guidelines/

基本原則

  • 代碼明晰是你主要目標。實體諸如方法和屬性,一次聲明,往往會被使用多次。故設計APIs時盡量使之清晰并且簡練。評估某個API設計是否合理,單從閱讀其聲明并不足以下結論,往往需要在真實示例下,才能確保它在上下文中是清晰正確的。

  • 明晰優先于簡練。盡管Swift代碼可以書寫的非常緊湊簡練,但實現最少代碼量并非是我們的目標。Swift代碼的簡練,只是強類型系統和自然降低功能樣板產生的附加效果而已。

  • 為每個聲明書寫文檔注釋。通過書寫文檔獲得的經驗見解會對你的設計產生深遠的影響,所以不要忽視之。

    如果你無法使用簡潔的術語描述你的APIs功能,那么你可能設計了錯誤的APIs。

詳情
  • 使用Swift的Markdown語法

  • 以摘要開始,描述聲明實體的功能。一般的,通過其聲明和摘要信息,API會被人清晰的理解。

    /// Returns `self`的"view"逆向集合。即包含相同元素,但順序相反。
         func reversed() -> ReverseCollection
    
    • 專注于摘要:這是最為重要的部分。很多優秀的文檔注釋只包含一個優質的摘要,別無它物。

    • 盡可能使用單句片段,并以句點結束。不要使用完整冗長的句子。

    • 描述方法或函數的功能和返回值,忽略無意義的null功能和Viod返回:

    /// 在self起始處插入 newHead
    mutating func prepend(_ newHead: Int)
    /// 返回一個包含head并由self跟隨的列表。func prepending(_ head: Element) -> List
    /// 若self非空,刪除并返回第一個元素;
    /// 否則返回nil
    mutating func popFirst() -> Element?
    ```
    提示:在極少數情況下,如上面的popFirst,摘要是由分號分隔的多個句子片段組成。

    • 下標注釋:即描述下標訪問的內容:

      /// 訪問下標為 第`index`個的元素。
                subscript(index: Int) -> Element { get set }
      
    • 構造方法:即描述初始化方法創建的內容:

      /// 創建實例:該實例中包含n個`x`。
                init(count n: Int, repeatedElement x: Element)
      
    • 其它聲明類場景,所聲明的實體務必描述清晰。

      /// 集合對象:在任何位置均支持同等高效的插入、刪除操作。
                struct List {
                  /// `self`非空,`self`的首個元素;
                  ///否則,返回`nil`。
                    var first: Element?
              ...
      
  • (可選),連續使用一個或多個段落和項目符號項。段落用空行分隔并使用完整的句子。

    /// 將`items`中每個元素的文本表示,執行標準輸出。   ← 摘要
        ///                                              ← 空行
        /// 每個元素`x`的文本表示均由`String(x)`表達式生成 ← 補充說明
        ///
        /// - 參數 separator: 元素之間的打印文本。           ?
        /// - 參數 terminator: 結尾打印的文本               ? 參數部分
        ///                                              ?
        /// - 備注: 若不要在結尾新起一行,
        ///則置`terminator: ""`即可。                      ?
        ///- 其它關聯: `CustomDebugStringConvertible`,     ? 命令符號
        ///   `CustomStringConvertible`, `debugPrint`.   ?
          public func print(
                _ items: Any..., separator: String = " ", terminator: String = "\n")
    

    流行的IDE工具(如Xcode)對以下關鍵字開頭的項目符號進行了特殊的處理:

    |關鍵字|||
    |:--:|:--:|:--:|:--:|
    |Attention|Author|Authors|Bug|
    |Complexity|Copyright|Date|Experiment|
    |Important|Invariant|Note|Parameter|
    |Parameters|Postcondition|Precondition|Remark|
    |Requires|Returns|SeeAlso|Since|
    |Throws|ToDo|Version|Warning|

命名

提高明晰方法
  • 為了便于閱讀代碼,在進行命名時,要涵蓋所有必須的單詞,以避免歧義

    例如,一個方法:在集合中刪除給定位置的元素。

    code1:?
    extension List {
          public mutating func remove(at position: Index) -> Element
        }
    employees.remove(at: x)
    

    如果我們從方法簽名中省略單詞at,則可能使讀者認為該方法是用于搜索并刪除等于x的元素,而不是使用x來指示要刪除的元素的位置。

    code2:?
    employees.remove(x) // 不清晰:是刪除x嗎?
    
  • 務必忽略不必須的單詞。命名中的每個單詞都應在使用場景中傳達重要的信息。

    有時需要更多的單詞來闡明意圖或消除歧義,但應省略那些眾所周知的冗余詞。特別是要省略那些僅為重復類型信息的單詞。

    code:?
    public mutating func removeElement(_ member: Element) -> Element?
        allViews.removeElement(cancelButton)
    

    在該示例中,Element在調用場景中沒有傳達任何重要信息,故該API可優化為:

    code:?
    public mutating func remove(_ member: Element) -> Element?
        allViews.remove(cancelButton) // clearer
    

    有時,重復類型信息對于避免歧義是有必要的。但通常而言,最好使用描述參數角色而不是其類型的單詞。有關詳細信息,請參閱下一項。

  • 根據角色來命名變量,參數和關聯類型,而不是根據類型約束。

    code:?
    var string = "Hello"
          protocol ViewController {
              associatedtype ViewType : View
        }
      class ProductionLine {
        func restock(from widgetFactory: WidgetFactory)
        }
    

    以這種方式重新定位類型名稱無法優化清晰度和表現力。相反,努力選擇一個表達實體角色的名稱反而更好。

    code: ?
    var greeting = "Hello"
        protocol ViewController {
            associatedtype ContentView : View
      }
    class ProductionLine {
        func restock(from supplier: WidgetFactory)
      }
    

    如果關聯類型與其協議約束緊密綁定以使協議名稱為角色,請通過將Protocol附加到協議名稱來避免沖突:

    protocol Sequence {
          associatedtype Iterator : IteratorProtocol
        }
        protocol IteratorProtocol { ... }
    
  • 補償弱類型信息以闡明參數的作用。

    尤其當參數類型是NSObjectAnyAnyObject或諸如Int或String的基本類型時,類型信息和在使用處的上下文可能無法完全傳達意圖。 在此示例中,聲明可能是明確的,但使用點是模糊的:

    code:?
    func add(_ observer: NSObject, for keyPath: String)
        grid.add(self, for: graphics) // 模糊
    

    為了恢復清晰度,在每個弱類型參數前面加上描述其角色的名詞:

    code:?
    func addObserver(_ observer: NSObject, forKeyPath path: String)
          grid.addObserver(self, forKeyPath: graphics) // 清晰
    
力求流暢使用
  • 使用標準英語語法規則來命名方法和函數。

    code: ?
    x.insert(y, at: z)          “x, insert y at z”
         x.subViews(havingColor: y)  “x's subviews having color y”
        x.capitalizingNouns()       “x, capitalizing nouns”
    
    code: ?
    x.insert(y, position: z)
        x.subViews(color: y)
        x.nounCapitalize()
    

    在第一個或第二個參數之后,當這些參數不是調用方法的核心時,流利性降級是可以接受的。即如果不影響方法要表達的含義,那可以簡化第一個或者前兩個參數,這樣使用起來更加流暢。

    AudioUnit.instantiate(
        with: description, 
        options: [.inProcess], completionHandler: stopProgressBar)
    
  • make前綴開始工廠方法的名稱命名。如 x.makeIterator()。

  • 構造方法和工廠方法調用的第一個參數不應該形成以基本名稱開頭的短語。如 x.makeWidget(cogCount: 47)。

    例如,這些調用方法的第一個參數不會作為與基本名稱相同的短語的一部分讀取:

    code: ?
    let foreground = Color(red: 32, green: 64, blue: 128)
        let newPart = factory.makeWidget(gears: 42, spindles: 14)
        let ref = Link(target: destination)
    

    在以下示例中,API作者嘗試使用第一個參數創建語法連續性:

    code: ?
    let foreground = Color(havingRGBValuesRed: 32, green: 64, andBlue: 128)
        let newPart = factory.makeWidget(havingGearCount: 42, andSpindleCount: 14)
        let ref = Link(to: destination)
    

    實際上,此準則以及參數標簽的準則意味著第一個參數將具有標簽,除非調用正在執行值保留類型轉換

    let rgbForeground = RGBColor(cmykForeground)
    
  • 根據副作用命名函數和方法。

    • 那些沒有副作用的函數和方法應該讀作是一個名詞詞組。如x.distance(to: y), i.successor().

    • 那些有副作用的函數和方法應該讀作是一個命令式的動詞短語。如print(x), x.sort(), x.append(y)

    • 命名名稱一致的Mutating/nonmutating方法對。變異方法通常具有一個類似語義的非突變變體,但返回新值而不是就地更新實例。

      • 當操作方法由動詞自然描述時,使用動詞對變異方法進行命名,而應用“ed”或“ing”后綴來命名對應的其非變異方法。
      Mutating Nonmutating
      x.sort() z = x.sorted()
      x.append(y) z = x.appending(y)
   * 更傾向于使用[動詞的過去分詞](https://en.wikipedia.org/wiki/Participle)命名非變異變體(通常附加`ed`)

    ```
    /// 即刻逆向 `self`。
            mutating func reverse()
            /// 返回self的逆向拷貝。
            func reversed() -> Self
            ...
          x.reverse()
          let y = x.reversed()
    ```
    
    * 當添加`ed`不具有語法性,因為動詞具有直接對象時,使用動詞的當前分詞命名非變異變體,通過附加“ing”。(應為語法問題)

    ```
    /// 過濾掉self中空行
            mutating func stripNewlines()
          /// 返回self的拷貝,該拷貝過濾掉了所有的空行。
          func strippingNewlines() -> String
          ...
        s.stripNewlines()
        let oneLine = t.strippingNewlines()
    ```
    
* 當操作方法由名詞描述時,使用名詞作為非突變方法。并使用`form`前綴來命名其對應的變異方法。

| Nonmutating | Mutating |
|:--:|:--:|
|x = y.union(z)| y.formUnion(z)|
|j = c.successor(i)|c.formSuccessor(&i)|
  • 當使用非突變方法時,布爾方法和屬性的使用在接收者看來,應為斷言的形式。如:x.isEmpty, line1.intersects(line2).
  • 描述類的協議應該以名詞命名。如Collection
  • 功能類的協議應以后綴為ableibleing的單詞命名。如Equatable, ProgressReporting
  • 其它類型,諸如屬性、變量、常量應以名詞來命名。
更好的使用術語

名詞 - 在特定領域或專業中具有精確、專門意義的詞或短語。

  • 如果一個更常見的詞語同樣傳達了相同意義,則避免使用模糊術語 。如果皮膚能夠闡述您的目的,請不要說表皮。藝術品是一種必不可少的溝通工具,但只應用于捕捉原本會丟失的重要意義。

  • 如果您使用藝術術語,請堅持使用它既定的意義

    使用技術術語而不是更常見的單詞,其唯一原因是它可以更精確地表達一些本來會模棱兩可或不清楚的東西。因此,API應嚴格使用術語。

    • 勿使專家驚訝。如果我們為眾所周知的術語發明了新的含義,任何已經熟悉它的人都會感到驚訝或憤怒。
    • 不要迷惑初學者:任何試圖學習該術語的人都可能會進行網絡搜索并找到其傳統的原始意義。
  • 避免使用縮寫。縮寫,尤其是非標準的縮寫,實際上是術語,因為理解依賴于正確地將它們翻譯成非縮寫形式。

    您使用的任何縮寫的含義,都可在網絡找到。即眾所周知的含義。

  • 擁抱先例。不要以犧牲與現有文化的一致性為代價來為初學者優化術語。

    命名連續數據結構為Array比使用簡單術語(如List)更好,即使初學者可能更容易理解List的含義。 Array是現代計算的基礎,因此每個程序員都應該知道 - 或者很快就會學到 - Array的概念。 使用大多數程序員都熟悉的術語,他們的問題搜索將很快得到解決。

    在特定的編程域中,例如數學,諸如sin(x)之類的廣泛使用的術語,要優于諸如verticalPositionOnUnitCircleAtOriginOfEndOfRadiusWithAngle(x)的解釋性短語。 請注意,在這種情況下,先例超過了指南中避免縮寫的規定:雖然完整的單詞是sine,但是sin(x)在程序員中已經普遍使用了幾十年,并且在幾個世紀的數學家中也是如此。

代碼規范

通用規范
  • 任何復雜度不是O(1)的計算屬性,均要注釋。人們通常認為屬性訪問不涉及重要的計算,因為他們在心理上將屬性作為存儲類型了。當一反常態時,需要備注提醒。

  • 首選方法和屬性實現而非函數。自由函數僅在以下特例中使用:

    1. 無顯式self:

      min(x,y,z)

    2. 函數是無約束的泛型時:

      print(x)

    3. 函數語法是已建立的域表示法的一部分時:

      sin(x)

  • 命名規范:類型和協議的命名遵循大駝峰命名法,其他一切都遵循小駝峰命名法。

    縮略語和首字母縮略詞:通常在美式英語中顯示為大寫.應根據拼寫規范統一大寫或小寫。

    var utf8Bytes: [UTF8.CodeUnit]
        var isRepresentableAsASCII = true
        var userSMTPServer: SecureSMTPServer
    

    其他首字母縮略詞應視為普通詞:

    var radarDetector: RadarScanner
        var enjoysScubaDiving = true
    
  • 當方法具有相同的基本含義或在不同的域中操作時,方法可以共用基本名稱。

    例如,以下方案值得推薦,因為這些方法的功能基本上是相同的:

    code: ?
    extension Shape {
        /// 返回 `true` ,假如 `other` 點在`self`面積之內.
        func contains(_ other: Point) -> Bool { ... }
    
        /// 返回 `true` 假如 `other` 圖形完全在 `self`之內.
        func contains(_ other: Shape) -> Bool { ... }
    
        /// 返回 `true` 假如 `other`線條在 `self`之內.
      func contains(_ other: LineSegment) -> Bool { ... }
      }
    

    由于幾何類型和集合是不同的域,因此在同一程序中也可以:

    code: ?
    extension Collection where Element : Equatable {
        /// 返回 `true` 假如 `self` 包含一個同 `sought`相同的元素.
        func contains(_ sought: Element) -> Bool { ... }
      }
    

    當然,這些索引方法具有不同的語義,并且應該以不同的方式命名:

    code: ?
    extension Database {
        /// 重建數據庫的搜索索引
        func index() { ... }
    
        /// 返回給定表中的第n行
        func index(_ n: Int, inTable: TableID) -> TableRow { ... }
      }
    

    最后,避免“在返回類型上重載”,因為它會在存在類型推斷時引起歧義:

    code: ?
    extension Box {
          /// 返回存儲在`self`中的`Int`值,否則,返回`nil`
        func value() -> Int? { ... }
    
        /// 返回存儲在`self`中的`String `值,否則,返回`nil` 
        func value() -> String? { ... }
        }
    
參數

func move(from start: Point, to end: Point)

  • 選擇參數名稱以供文檔注釋。即使參數名稱沒有出現在函數或方法的使用點,它們也起著重要的解釋作用。

    選擇這些名稱可以使文檔易于閱讀。例如,以下這些名稱使文檔閱讀理解更加自然:

    code: ?
    /// 返回滿足`predicate`斷言的,并包含`self`的元素集合 
        func filter(_ predicate: (Element) -> Bool) -> [Generator.Element]
        /// 以`newElements`替換給定 `subRange`范圍的集合。
        mutating func replaceRange(_ subRange: Range, with newElements: [E])
    

    當然,以下例子使文檔變得笨拙和不合語法:

    code: ?
    /// 返回滿足`includedInResult `斷言的,并包含`self`的元素集合 .
        func filter(_ includedInResult: (Element) -> Bool) -> [Generator.Element]
        /// 以`with `替換給定 `r `范圍的集合。
        mutating func replaceRange(_ r: Range, with: [E])
    
  • 一般場景時,合理利用默認參數。某一參數在大多數場景下都是某個固定值,比較適合設置默認參數。

    默認參數通過隱藏不相關的信息來提高可讀性。例如:

    code: ?
    let order = lastName.compare(
        royalFamilyName, options: [], range: nil, locale: nil)
    

    可以更為簡潔:

    let order = lastName.compare(royalFamilyName)
    

    默認參數通常比使用方法集更可取,因為它們會降低理解API的認知負擔。

    code: ?
    extension String {
        /// ...description...
        public func compare(
           _ other: String, options: CompareOptions = [],
           range: Range? = nil, locale: Locale? = nil
      ) -> Ordering
    }
    

    上述方案可能并不簡單,但相比方法集,足夠簡潔:

    code: ?
    extension String {
        /// ...description 1...
        public func compare(_ other: String) -> Ordering
        /// ...description 2...
        public func compare(_ other: String, options: CompareOptions) -> Ordering
        /// ...description 3...
      public func compare(
           _ other: String, options: CompareOptions, range: Range) -> Ordering
      /// ...description 4...
      public func compare(
         _ other: String, options: StringCompareOptions,
         range: Range, locale: Locale) -> Ordering
      }
    

    方法集合的每個成員都需要單獨的文檔注釋,并由用戶理解。用戶需要完全理解它們,才能選擇最優方法。 偶爾也會出現令人驚訝的問題 - 例如,foo(bar:nil)foo()并不總是同等的 - 文檔繁瑣,差異卻微小。 使用含默認值單一方法可提供極其優越的編程體驗。

  • 默認參數應放置在參數列表的末尾。沒有默認值的參數通常對于方法的語義更為重要,并且在調用方法時提供穩定的初始使用模式。

參數標簽

func move(from start: Point, to end: Point)

x.move(from: x, to: y)

  • 在無法有效區分參數時省略所有標簽

    如:min(number1, number2),zip(sequence1, sequence2).

  • 在執行值保留類型轉換的構造器中,省略第一個參數標簽。

    第一個參數應該始終是轉換的來源:

    extension String {
        // 將`x`轉換為給定基數中的文本表示
      init(_ x: BigInt, radix: Int = 10)   ← Note the initial underscore
      }
      text = "The value is: "
      text += String(veryLargeNumber)
      text += " and in hexadecimal, it's"
      text += String(veryLargeNumber, radix: 16)
    

    但是,在“縮小”類型轉換中,添加描述縮小的標簽是有必要的。

    extension UInt32 {
        /// Creates an instance having the specified `value`.
        init(_ value: Int16)            ← Widening, so no label
        /// 創建一個具有最低32位“source”的實例 `source`.
        init(truncating source: UInt64)
        /// 創建近似于`valueToApproximate`的實例 
        init(saturating valueToApproximate: UInt64)
      }
    

    值保持類型轉換單態的,即原始值的每個差異均會導致結果值的差異。 例如,從Int8到Int64的轉換是值保留的,因為每個不同的Int8值都轉換為不同的Int64值;但是,在相反方向上的轉換不能保留值:Int64具有比Int8中表示的值更多的可能值。

    注意:檢索原始值的能力與轉換是否有保留值無關。

  • 當第一個參數構成介詞短語的一部分時,給它設置一個參數標簽。參數標簽通常應該從介詞開始,如x.removeBoxes(havingLength: 12)

    當前兩個參數表示單個抽象的一部分時會出現異常:

    code: ?
    a.move(toX: b, y: c)
        a.fade(fromRed: b, green: c, blue: d)
    

    這種情況,在介詞后添加參數標簽,以保持抽象概念清晰。

    code: ?
    a.moveTo(x: b, y: c)
        a.fadeFrom(red: b, green: c, blue: d)
    
  • 否則,如果第一個參數構成語法短語的一部分,則省略其標簽.將前置的單詞附加到基本名稱上,例如, x.addSubview(y)
本指南認為如果第一個參數不構成語法短語的一部分,它應該有一個標簽。

```
?
view.dismiss(animated: false)
    let text = words.split(maxSplits: 12)
    let studentsByName = students.sorted(isOrderedBefore:                     
    Student.namePrecedes)
```

請注意,短語傳達正確的含義非常重要。以下可能表達會錯誤的觀點。

```
?
view.dismiss(false)   Don't dismiss? Dismiss a Bool?
    words.split(12)       Split the number 12?
```

另請注意,可以省略含默認值的參數。在這種情況下,不要形成語法短語的一部分,因此它們應始終具有標簽。
  • 為其它所有參數添加參數標簽。

特別說明

  • 在API中為tuple元組成員添加參數標簽,命名閉包參數。

    這些名稱具有很好的解釋能力,可以從文檔注釋中引用,并提供對元組成員的訪問。

    /// 確保我們有requestedCapacity最后一個元素的唯一引用的存儲單元。 
        ///
        /// 如果需要更多存儲空間,則調用`allocate` 。且分配的字節數`bygerCount`等于最大數。
        ///
        /// - Returns:
        ///   - reallocated: `true` iff a new block of memory
      ///     was allocated.
      ///   - capacityChanged: `true` iff `capacity` was updated.
    mutating func ensureUniqueStorage(
        minimumCapacity requestedCapacity: Int, 
          allocate: (_ byteCount: Int) -> UnsafePointer<Void>
      ) -> (reallocated: Bool, capacityChanged: Bool)
    

    用于閉包參數的名稱,應如頂級函數的參數名稱一樣。在閉包參數調用處不應出現參數標簽。

  • 使用不受約束的多態性(例如AnyAnyObject無約束的通用參數)時要格外小心,以避免重載集中出現歧義。

    如考慮重載集:

    ?
    struct Array {
          ///  在 `self.endIndex`處插入`newElement`.
          public mutating func append(_ newElement: Element)
    
          /// 在`self.endIndex`順序插入 `newElements`內容。
          public mutating func append(_ newElements: S)
            where S.Generator.Element == Element
        }
    

這些方法形成一個語義簇,并且參數類型明顯不同。但是,當Element為Any時,單個元素可以與元素序列具有相同的類型。

```
?
var values: [Any] = [1, "a"]
    values.append([2, 3, 4]) // [1, "a", [2, 3, 4]] or [1, "a", 2, 3, 4]?
```

為消除歧義,可以更明確地命名第二個重載方法。

```
?
struct Array {
      /// 在 `self.endIndex`處插入`newElement`.
      public mutating func append(_ newElement: Element)

      /// 在`self.endIndex`順序插入 `newElements`內容
      public mutating func append(contentsOf newElements: S)
        where S.Generator.Element == Element
    }
```

注:如何命名以更好地匹配文檔注釋。實際上是在編寫文檔注釋時,得到了API作者的注意。

更多

  • 紕漏之處,歡迎斧正。
  • 更多內容請關注公眾號:IT互聯網自習室
IT互聯網自習室.jpg
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容