[译]Swift代码格式

323 阅读8分钟

原文链接 Swift Code Formatters

我刚离开一家时髦的咖啡店。里面挤满了iOS开发者,他们彼此窃窃私语,说他们迫不及待地想让苹果发布Swift的官方风格指南和格式化程序。

最近,社区一直在讨论**托尼·阿莱瓦托戴夫·亚伯拉罕**关于为斯威夫特语言采用官方风格指南和格式工具的提议。

数百名社区成员参与了最初的推销**提议**就像所有的风格问题一样,观点是强烈的,每个人都有。幸运的是,来自社区的讨论通常具有建设性和洞察力,阐明了各种观点、用例和关注点。

自从我们的文章在3月份首次发表以来,“SE-0250:快速代码样式指南和格式”的提案开始了正式审查;这一过程后来被**暂停**,将在未来某个时候重新考虑。

尽管如此,Swift代码格式仍然是许多开发人员感兴趣的话题。因此,本周在NS Hipster上,我们将再次审视当前可用的Swift格式化器领域——包括作为提案一部分发布的快速格式化工具——并看看它们是如何叠加的。从那时起,我们将后退一步,试着正确看待一切。

但首先,让我们从一个问题开始:

什么是代码格式?

出于我们的目的,我们将代码格式定义为对代码所做的任何更改,这些更改在不改变其行为的情况下使代码更容易理解。虽然这一定义扩展到等价形式的差异,(例如:[国际]对数组<国际>),我们将把这里的讨论限制在空白和标点符号上。

像许多其他编程语言一样,Swift在接受换行符、制表符和空格方面相当自由。大多数空白都是微不足道的,从编译器的角度来看,对周围的代码没有影响。

当我们使用空格在不改变代码行为的情况下使代码更容易理解时,这是二级**符号**的一个例子;当然,主要符号是代码本身。

在正确的地方放置足够多的分号,你可以在一行代码中编写几乎任何东西。但是在所有条件相同的情况下,为什么不使用水平和垂直空格来以一种我们更容易理解的方式可视化地构造代码,对吗?

不幸的是,编译器对空白的接受性质造成的模糊性经常会在程序员中引起混乱和分歧:“我应该在花括号前添加换行符吗?我如何分解超出编辑器宽度的语句?"

组织经常编纂如何处理这些问题的指导方针,但它们往往没有得到充分说明、执行不力和过时。代码格式化程序的作用是自动强制执行一组约定,以便程序员可以抛开他们的差异,着手解决实际问题。

工具比较

斯威夫特社区从一开始就考虑了风格问题。样式指南从Swift的最初几天就已经存在,各种开源工具也已经存在,可以自动格式化代码以匹配它们。

为了了解Swift代码格式化程序的当前状态,我们将查看以下四个工具:

ProjectRepository URLSwiftFormatgithub.com/nicklockwoo…](#swiftlint)github.com/realm/Swift…](#swift-format)github.com/google/swif…

为了建立比较的基础,我们设计了以下代码示例来评估每个工具(使用它们的默认配置):

        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")

尽管代码格式包括广泛的可能的语法和语义转换,但我们将关注换行符和缩进,我们认为这是任何代码格式器的基线要求。

Swift Format

首先是**Swift Format**,一个自我描述的有用工具。

安装

Swift Format通过**自制以及薄荷可可豆**分发。

您可以通过运行以下命令安装它:

$ brew install swiftformat

此外,Swift Format还提供了一个Xcode源代码**编辑器扩展,可以在编辑器扩展中找到,您可以使用它来在Xcode中重新格式化代码。或者,如果您是VSCode**的用户,您可以使用[]调用Swift Format(marketplace.visualstudio.com/items?项目>这个…

$ swiftformat Example.swift

Swift Format有多种规则,可以通过命令行选项单独配置,也可以使用配置文件进行配置。

示例输出

在我们的示例中使用缺省规则集运行斯威夫特格式命令会产生以下结果:

// swiftformat version 0.40.8struct 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.39.5中是固定的。干得好,尼克**!

性能

Swift Format始终是本文中测试的最快的工具,只需几毫秒即可完成。

$ time swiftformat Example.swift        0.03 real         0.01 user         0.01 sys

Swift Lint

接下来是Swift Lint,Swift开源社区的中流砥柱。有了100多个内置规则,**Swift Lint**可以对你的代码执行各种各样的检查——从对纯类协议更喜欢任何对象而不是类到所谓的“尤达条件规则”,后者规定变量放在比较运算符的左侧(也就是说,如果n==42,而不是如果42==n)。

顾名思义,Swift Lint主要不是一个代码格式化程序;它实际上是一个识别违反约定和滥用应用编程接口的诊断工具。然而,由于它的自动校正功能,它经常被用来格式化代码。

安装

您可以通过以下命令使用Homebrew安装Swift Lint:

$ brew install swiftlint

或者,您可以将Swift Lint与Cocoa Pod、Mint一起安装,或者作为独立的安装程序包(. pkg)安装。

使用方法

要将Swift Lint用作代码格式化程序,请运行自动更正子命令,传递--格式化选项和要更正的文件或目录。

$ swiftlint autocorrect --format --path Example.swift

示例输出

在我们的示例中运行前面的命令会产生以下结果:

// swiftlint version 0.32.0struct 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")

Swift Lint清除了最糟糕的缩进和间距问题,但保留了其他无关的空白(尽管它确实删除了文件的主要换行符,这很好)。同样,值得注意的是,格式化不是Swift Lint的主要调用;如果有什么不同的话,那只是提供可操作的代码诊断的附带条件。从“第一,不伤害他人”的角度来看,很难抱怨这里的结果。

性能

对于Swift Lint检查的所有内容,它都非常快——对于我们的例子来说,只需几分之一秒就能完成。

$ time swiftlint autocorrect --quiet --format --path Example.swift        0.09 real         0.04 user         0.01 sys

斯威夫特格式

在查看了当前可用的Swift格式化器的情况后,我们现在有了一个合理的基线来评估Tony Alle vato和Dave Abrahams提出的快速格式化工具。

安装

斯威夫特格式的代码目前托管在**谷歌Swift项目分叉的格式分支**上。您可以通过运行以下命令检查并从源代码构建它:

$ git clone https://github.com/google/swift.git swift-format$ cd swift-format$ git checkout format$ git submodule update --init$ swift build

为了您的方便,我们提供了一个自制公式,该公式基于**我们自己的谷歌分叉**,您可以使用以下命令安装:

$ brew install nshipster/formulae/swift-format

使用方法

运行斯威夫特格式命令,将一个或多个文件和目录路径传递给要格式化的斯威夫特文件。

$ swift-format Example.swift

斯威夫特格式命令还带有一个配置选项,它带有一个JSON文件的路径。目前,自定义快速格式行为的最简单方法是将默认配置转储到文件中,然后从那里开始。

$ swift-format -m dump-configuration > .swift-format.json

运行上面的命令将使用以下JSON填充指定的文件:

{  "blankLineBetweenMembers": {    "ignoreSingleLineProperties": true  },  "indentation": {    "spaces": 2  },  "lineBreakBeforeControlFlowKeywords": false,  "lineBreakBeforeEachArgument": true,  "lineLength": 100,  "maximumBlankLines": 1,  "respectsExistingLineBreaks": true,  "rules": {    "AllPublicDeclarationsHaveDocumentation": true,    "AlwaysUseLowerCamelCase": true,    "AmbiguousTrailingClosureOverload": true,    "AvoidInitializersForLiterals": true,    "BeginDocumentationCommentWithOneLineSummary": true,    "BlankLineBetweenMembers": true,    "CaseIndentLevelEqualsSwitch": true,    "DoNotUseSemicolons": true,    "DontRepeatTypeInStaticProperties": true,    "FullyIndirectEnum": true,    "GroupNumericLiterals": true,    "IdentifiersMustBeASCII": true,    "MultiLineTrailingCommas": true,    "NeverForceUnwrap": true,    "NeverUseForceTry": true,    "NeverUseImplicitlyUnwrappedOptionals": true,    "NoAccessLevelOnExtensionDeclaration": true,    "NoBlockComments": true,    "NoCasesWithOnlyFallthrough": true,    "NoEmptyAssociatedValues": true,    "NoEmptyTrailingClosureParentheses": true,    "NoLabelsInCasePatterns": true,    "NoLeadingUnderscores": true,    "NoParensAroundConditions": true,    "NoVoidReturnOnFunctionSignature": true,    "OneCasePerLine": true,    "OneVariableDeclarationPerLine": true,    "OnlyOneTrailingClosureArgument": true,    "OrderedImports": true,    "ReturnVoidInsteadOfEmptyTuple": true,    "UseEnumForNamespacing": true,    "UseLetInEveryBoundCaseVariable": true,    "UseOnlyUTF8": true,    "UseShorthandTypeNames": true,    "UseSingleLinePropertyGetter": true,    "UseSpecialEscapeSequences": true,    "UseSynthesizedInitializer": true,    "UseTripleSlashForDocumentationComments": true,    "ValidateDocumentationComments": true  },  "tabWidth": 8,  "version": 1}

在摆弄配置之后——比如将line长度设置为80的正确值*(不要@我)*——你可以这样应用它:

$ swift-format Example.swift --configuration .swift-format.json

示例输出

使用其默认配置,以下是快速格式如何格式化我们的示例:

// swift-format 0.0.1 (2019-05-15, 115870c)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")

*别动我的心!*😍我们可以不使用原始分号,但总的来说,这是非常无可争议的——这正是你想要的官方代码风格工具。

灵活输出

但是为了充分欣赏快速格式输出的优雅,我们必须在许多不同的列宽度上进行比较。

让我们看看它是如何处理这个新的代码示例的,其中充满了繁琐的UI Application委托方法和网址会话构造:

40列

import UIKit@UIApplicationMainclass AppDelegate: UIResponder,  UIApplicationDelegate{  var window: UIWindow?  func application(    _ application: UIApplication,    didFinishLaunchingWithOptions      launchOptions:      [UIApplication.LaunchOptionsKey:      Any]?  ) -> Bool {    let url = URL(      string:        "https://nshipster.com/swift-format"    )!    URLSession.shared.dataTask(      with: url,      completionHandler: {        (data, response, error) in        guard error == nil,          let data = data,          let response = response          as? HTTPURLResponse,          (200..<300).contains(          response.statusCode        ) else {          fatalError(            error?.localizedDescription              ?? "Unknown error"          )        }        if let html = String(          data: data,          encoding: .utf8        ) {          print(html)        }      }    ).resume()    // Override point for customization after application launch.    return true  }}

这种灵活性在工程环境中并不特别有用,在工程环境中,开发人员可以也应该充分利用他们的屏幕空间。但是对于我们这些从事技术写作的人来说,这是一个杀手级的功能,他们不得不与移动视口和页面边缘等东西搏斗。

性能

就性能而言,快速格式不会快到让人感觉瞬间,但也不会慢到成为问题。

$ time swift-format Example.swift        0.24 real         0.16 user         0.14 sys

结论:你不需要等待开始使用代码格式工具

决定我们作为一个社区想要采用哪些惯例是一个重要的对话,值得我们对任何提议的语言改变进行深思熟虑和认真的考虑。然而,是否应该有官方风格指南或权威代码格式工具的问题不应该阻止你今天为自己的项目采取措施!

我们强烈认为,大多数项目都可以通过采用代码格式工具来改进,只要它符合以下标准:

  • 它很稳定

  • 它很快(足够快)

  • 它产生合理的产量

根据我们对当前可用工具的调查,我们可以自信地说**Swift Format****和swift-**form都符合这些标准,并且适合在生产中使用。

(如果我们必须在两者之间做出选择,出于美学考虑,我们可能会选择快速格式。但是每个开发人员有不同的偏好,每个团队有不同的需求,你可能更喜欢别的东西。)

当你评估要纳入工作流程的工具时,如果你还没有尝试**Swift Lint**,你最好试试。Swift Lint以其链接能力,可以大大有助于系统地提高代码质量——尤其是对于那些更老、更大、有大量贡献者的项目。

关于代码风格的争论的问题在于它的规模和主观性。通过在我们今天的日常工作流程中采用这些工具,我们不仅受益于今天更好、更易维护的代码,而且我们可以帮助推进辩论,从模糊的感觉到对仍然存在的任何差距的精确观察。