[譯] Swift 代碼格式化

我剛離開了一家時髦的咖啡館。裏面有不少 iOS 開發者,他們互相竊竊私語,討論他們是多麼得等不及蘋果公司爲 Swift 發佈的官方風格指南和格式化程序。javascript

在過去的幾天裏,社區一直在討論 Tony AllevatoDave Abrahams 採用官方版的 Swift 格式化工具。html

數十名社區成員已經對 提案草案 進行了權衡。與全部樣式問題同樣,每一個人都有不一樣的意見。但幸運的是,來自社區的話語一般具備表明性和洞察力,其中清晰表達了各式各樣的觀點、用例以及關注點。前端

在撰寫本文時,彷佛不少但不是全部受訪者都對官方格式化表示贊同。那些同意給代碼格式化約束的人也但願有一個工具能夠自動診斷和修復不符合這些約束的代碼。可是,其餘一些人對這格式化的適用性和可配置性表示擔心。java

在本週的 NSHipster 上,咱們將討論一下關於當前可用的 Swift 格式化程序,包括做爲提案的一部分發布的 swift-format 工具,同時看看它們是如何進行處理的。而後咱們再權衡其中的利弊。android

首先,讓咱們從下面的一個問題開始:ios

什麼是代碼格式化?

咱們將代碼格式化定義爲對代碼所作的任何更改,以便在不改變其行爲的狀況下更容易理解代碼的內容。雖然這個定義延伸到等價形式的差別(例如 [Int]Array <Int>),咱們暫且將這裏的討論內容限制爲空格和標點符號。git

與許多其餘編程語言同樣,Swift 在接受換行符,製表符和空格時都很是自由。大多數空格都是可有可無的,從編譯器的角度來看對代碼沒有任何影響。程序員

當咱們使用空格來使代碼更易於理解而不改變其行爲時,此時空格就做爲一個 輔助符號。固然,主要符號是代碼自己。github

另外一種輔助表示法是語法高亮,這在咱們之前的 NSHipster 文章 中討論過.編程

雖然你能夠經過分號在一行代碼中寫幾乎任何東西,可是在其餘條件相同的狀況下,使用空格與換行來對齊代碼難道不是一種更易於理解而且直觀的方式嗎?

不幸的是,因編譯器接受空格的性質而產生的歧義每每會引發程序員之間對代碼的混淆和分歧:「我到底應不該該在大括號以前添加換行符?如何分解超出編輯器寬度的語句?」

一個公司一般會有本身的一套代碼規範,但它們一般不夠明確,執行力不強,並且可能已通過時。代碼格式化程序的做用是自動執行一組約定,以便程序員能夠拋開差別來把重心放到解決實際問題上。

格式化工具比較

Swift 社區從一開始就考慮過代碼格式問題,代碼書寫規範從 Swift 誕生之初就已經存在,各類開源工具也能夠經過規範來自動化格式化代碼。

你能夠看看如下四個工具來了解目前 Swift 代碼格式化程序的狀態:

項目 倉庫連接
SwiftFormat github.com/nicklockwoo…
SwiftLint github.com/realm/Swift…
Prettier with Swift Plugin github.com/prettier/pr…
swift-format(已提議) github.com/google/swif…

爲簡潔起見,本文僅討論一些可用的 Swift 格式化工具。若是你有興趣能夠了解如下更多內容:SwimatSwiftRewriterswiftfmt

爲了方便比較,咱們設計瞭如下代碼來評估每一個工具,工具都使用它們的默認配置:

struct ShippingAddress : Codable {
    var recipient: String
    var streetAddress : String
    var locality :String
    var region   :String;var postalCode:String
    var country:String

    init(recipient: String,        streetAddress: String,
         locality: String,region: String,postalCode: String,country:String)
    {
        self.recipient = recipient
        self.streetAddress = streetAddress
        self.locality  = locality
        self.region        = region;self.postalCode=postalCode
        guard country.count == 2, country == country.uppercased() else { fatalError("invalid country code") }
        self.country=country}}

let applePark = ShippingAddress(recipient:"Apple, Inc.", streetAddress:"1 Apple Park Way", locality:"Cupertino", region:"CA", postalCode:"95014", country:"US")
複製代碼

儘管代碼格式化包含各類可能的語法和語義轉換,但咱們將專一於換行和縮進的問題,咱們認爲這是任何代碼格式化程序的基礎要求。

誠然,本文中的性能基準並非很是嚴格。可是它們應該能夠做爲參考。咱們把秒做爲測量時間的單位,採用 2017 款配備 2.9 GHz Intel Core i7 處理器和 16 GB 2133 MHz LPDDR3 內存的 MacBook Pro。

SwiftFormat

首先是 SwiftFormat,它的簡介是一個頗有用的工具。

安裝

SwiftFormat 能夠經過 HomebrewMint 以及 CocoaPods 來安裝。

你能夠經過如下命令來安裝它:

$ brew install swiftformat
複製代碼

此外,SwiftFormat 還提供了一個 Xcode 拓展,你能夠經過在 Xcode 裏使用它進行格式化。或者若是你是 VSCode 用戶,你可使用 此插件

使用

swiftformat 命令會把在指定文件和目錄路徑中找到的全部 Swift 文件進行格式化。

$ swiftformat Example.swift
複製代碼

SwiftFormat 有不少規則,你能夠經過命令行選項或使用配置文件單獨配置。

樣例輸出

使用默認配置運行 swiftformat 後,你會獲得如下輸出:

// swiftformat version 0.39.5
struct ShippingAddress: Codable {
    var recipient: String
    var streetAddress: String
    var locality: String
    var region: String; var postalCode: String
    var country: String

    init(recipient: String, streetAddress: String,
         locality: String, region: String, postalCode: String, country: String) {
        self.recipient = recipient
        self.streetAddress = streetAddress
        self.locality = locality
        self.region = region; self.postalCode = postalCode
        guard country.count == 2, country == country.uppercased() else { fatalError("invalid country code") }
        self.country = country
    }
}

let applePark = ShippingAddress(recipient: "Apple, Inc.", streetAddress: "1 Apple Park Way", locality: "Cupertino", region: "CA", postalCode: "95014", country: "US")
複製代碼

正如你所見,格式化明顯改進了原始版本。每一行都根據其範圍縮進,標點符號間的聲明都有一致的間距。屬性聲明中的分號和初始化參數中的換行都被保留。可是,大括號沒有像預期的那樣被移動到單獨一行,Nick0.39.5 修復了它。

性能

SwiftFormat 在本文中測試工具中始終是最快的,它能在幾毫秒內完成處理。

$ time swiftformat Example.swift
        0.03 real         0.01 user         0.01 sys
複製代碼

SwiftLint

接下來是 SwiftLint,它是 Swift 開源社區的支柱。其中包含了超過 100 個規則,SwiftLint 能夠對你的代碼執行各類各樣的檢查,從將 AnyObject 優於 class(僅用於類的協議)到所謂的「尤達條件式」,其中規定變量應放在比較運算符的左側(即 if n == 42 而非 if 42 == n)。

顧名思義,SwiftLint 主要不是代碼格式化程序,它確實是一種識別濫用慣例和誤用 API 的診斷工具。正是因爲具有自動校訂功能,它經常被用於格式化代碼。

安裝

你能夠經過如下命令使用 Homebrew 來安裝它:

$ brew install swiftlint
複製代碼

或者你也能夠經過 CocoaPodsMint 或者 獨立 pkg 安裝包 來安裝。

使用

要用 SwiftLint 來進行代碼格式化,請運行 autocorrect 子命令,--format 參數爲須要執行的文件或目錄。

$ swiftlint autocorrect --format --path Example.swift
複製代碼

樣例輸出

運行上述命令,你將獲得如下輸出:

// swiftlint version 0.31.0
struct ShippingAddress: Codable {
    var recipient: String
    var streetAddress: String
    var locality: String
    var region: String;var postalCode: String
    var country: String

    init(recipient: String, streetAddress: String,
         locality: String, region: String, postalCode: String, country: String) {
        self.recipient = recipient
        self.streetAddress = streetAddress
        self.locality  = locality
        self.region        = region;self.postalCode=postalCode
        guard country.count == 2, country == country.uppercased() else { fatalError("invalid country code") }
        self.country=country}}

let applePark = ShippingAddress(recipient: "Apple, Inc.", streetAddress: "1 Apple Park Way", locality: "Cupertino", region: "CA", postalCode: "95014", country: "US")
複製代碼

SwiftLint 能夠清除最糟糕的縮進和行內間距問題,同時保留其餘無關的空格。值得注意的是,格式化不是 SwiftLint 的主要做用,通常狀況下,它只是附帶提供可操做的代碼診斷。從 「首先,沒有任何反作用」 的角度來看,你沒必要在這裏抱怨它處理的結果。

性能

SwiftLint 檢查的全部內容很是高效,在咱們的示例中它只需幾分之一秒便可完成。

$ time swiftlint autocorrect --quiet --format --path Example.swift
        0.11 real         0.05 user         0.02 sys
複製代碼

Swift Plugin 美化

若是你沒有使用過 JavaScript(如 上週文章 中所述),這多是你據說過的第一篇 Prettier 相關的文章。反之,若是你沉浸在 ES6,React 和 WebPack 的世界中,你幾乎確定會特別依賴它。

Prettier 在代碼格式化程序中是獨一無二的,由於它體現了代碼的美學,用換行來分隔代碼,就好像寫詩同樣。

多虧了一個在開發中的 插件架構,你能夠在其餘語言上使用它,其中 包括了 Swift

Swift 的 Prettier 插件很是重要,可是當它遇到一個沒有規則的語法標記時會崩潰(好比 EnumDecl 😩)。然而,正如你將在下面看到的那樣,到目前爲止它的功能仍是很強大以致於咱們不能忽視它,因此把它放在本文格式化工具 PK 中是值得的。

安裝

要使用 Prettier 及其 Swift 插件,你將涉及到 Node。雖然有不少方法能夠安裝它,但咱們仍是最喜歡 Yarn 😻。

$ brew install yarn
$ yarn global add prettier prettier/plugin-swift
複製代碼

使用

如今環境變量 $PATH 裏已經能夠訪問 prettier 命令行工具,你能夠傳入文件或路徑來運行它。

$ prettier Example.swift
複製代碼

樣例輸出

如下是在咱們的前面的例子中使用 Prettier 運行最新版本的 Swift 插件獲得的輸出:

// prettier version 1.16.4
// prettier/plugin-swift version 0.0.0 (bdf8726)
struct ShippingAddress: Codable {
    var recipient: String
    var streetAddress: String
    var locality: String
    var region: String
    var postalCode: String
    var country: String

    init(
        recipient: String,
        streetAddress: String,
        locality: String,
        region: String,
        postalCode: String,
        country: String
    ) {
        self.recipient = recipient
        self.streetAddress = streetAddress
        self.locality = locality
        self.region = region;
        self.postalCode = postalCode
        guard country.count == 2, country == country.uppercased() else {
            fatalError("invalid country code")
        }
        self.country = country
    }
}

let applePark = ShippingAddress(
    recipient: "Apple, Inc.",
    streetAddress: "1 Apple Park Way",
    locality: "Cupertino",
    region: "CA",
    postalCode: "95014",
    country: "US"
)
複製代碼

Prettier 將本身稱爲 「一個自覺得是的代碼格式化程序」。實際上,它的配置方式很少,只有兩個選項:「常規代碼」 或 「更漂亮的代碼」。

如今,你可能會面對不少垂直的對齊,但你不得不認可這段代碼看起來確實很棒。全部的語句都有均勻間隔、縮進以及對齊,很難相信這是自動實現的。

固然,咱們以前的警告仍然適用:這仍然在開發中,並不適合生產環境使用,並且它還有一些性能問題。

性能

說到底,Prettier 比本文討論的其餘工具都慢一到兩個數量級。

$ time prettier Example.swift
    1.14 real         0.56 user         0.38 sys
複製代碼

目前還不清楚這究竟是語言障礙仍是優化不良的結果,Prettier 慢得足以帶來很大的問題。

目前,咱們建議僅將 Prettier 用於一次性格式化任務,例如編寫文章和書籍的代碼。

swift-format

在瞭解可用於 Swift 格式化程序的現狀後,咱們如今有了一個合理的標準來評估 Tony Allevato 和 Dave Abrahams 提出的 swift-format 工具。

安裝

swift-format 的代碼目前託管在 Google fork 的 Swift 的 format 分支 上。你能夠經過運行如下命令來下載而且從源代碼編譯它:

$ git clone https://github.com/google/swift.git swift-format
$ cd swift-format
$ git submodule update --init
$ swift
複製代碼

爲了讓你方便使用,咱們提供了一個自制的公式,該公式來自 咱們本身 Google 倉庫上的 fork,你能夠經過下面的命令來安裝:

$ brew install nshipster/formulae/swift-format
複製代碼

使用

運行 swift-format 命令,傳入要格式化的 Swift 文件或目錄。

$ swift-format Example.swift
複製代碼

swift-format 命令還採用了 --configuration 選項,該參數須要傳入一個 JSON 文件。目前,定製 swift-format 行爲的最簡單方法就是將默認配置轉儲到文件裏。

$ swift-format -m dump-configuration .swift-format.json
複製代碼

建立以下的 JSON 文件,在運行上述命令時傳入:

{
  "blankLineBetweenMembers": {
    "ignoreSingleLineProperties": true
  },
  "indentation": {
    "spaces": 2
  },
  "lineLength": 100,
  "maximumBlankLines": 1,
  "respectsExistingLineBreaks": true,
  "tabWidth": 8,
  "version": 1
}
複製代碼

在配置完以後,你應該這樣使用:

$ swift-format Example.swift --configuration .swift-format.json
複製代碼

樣例輸出

使用其默認配置,這裏是 swift-format 格式化後的輸出:

// swift-format version 0.0.1
struct ShippingAddress: Codable {
  var recipient: String
  var streetAddress: String
  var locality: String
  var region   :String;
  var postalCode: String
  var country: String

  init(
    recipient: String, streetAddress: String,
    locality: String, region: String, postalCode: String, country: String
  )
  {
    self.recipient = recipient
    self.streetAddress = streetAddress
    self.locality = locality
    self.region = region
    self.postalCode = postalCode
    guard country.count == 2, country == country.uppercased() else {
      fatalError("invalid country code")
    }
    self.country = country
  }
}

let applePark = ShippingAddress(
  recipient: "Apple, Inc.", streetAddress: "1 Apple Park Way", locality: "Cupertino", region: "CA",
  postalCode: "95014", country: "US")
複製代碼

隨着 0.0.1 版本的發佈,這好像頗有但願!咱們能夠在沒有原始分號的狀況下實現,也不用太關心 region 屬性的冒號位置,但總的來講,這是無可非議的,它正是你想要的官方代碼格式化工具。

性能

在性能方面,swift-format 目前處於中間位置,不快也不慢。

$ time swift-format Example.swift
        0.51 real         0.20 user         0.27 sys
複製代碼

根據咱們有限的初步調查,swift-format 彷佛提供了一套合理的格式約定。但願將來它能建立出更加生動的例子來幫助咱們理解其中的格式化規則。

不管如何,看到提案如何發展以及圍繞這些問題展開討論都將是一件頗有趣的事。


NSMutableHipster

若是你有其餘問題,歡迎給咱們提 Issuespull requests

這篇文章使用 Swift 5.0.。你能夠在 狀態頁面 上查找全部文章的狀態信息。

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索