前言
代码风格可能是一个有争议的话题,并且在开发人员之间引发一些激烈的讨论。使用工具强制执行一套代码风格规则对于避免一些争论,以及确保在整个项目中保持代码风格的一致性非常有帮助。SwiftLint 可以很容易的整合进 Xcode 项目中,以便在编译时将代码风格冲突标记为警告或者错误。
使用 Xcode 集成 SwiftLint
你可以在 Github上 获得SwiftLint。它可以使用多种方式安装,比如,直接下载 SwiftLint.pkg 包,或者使用HomeBrew命令行。
1 | brew install swiftlint |
安装 SwiftLint 后,可以通过在主 app target 的Build Phase
(构筑阶段)下添加一个Run Phase
(运行阶段)的运行脚本,来集成进 Xcode 项目。点击+
号按钮,选择”New Run Script Phase”,添加下面的脚本。在 silicon Macs (搭载M1芯片)上需要添加export
语句,因为HomeBrew
的二进制文件默认安装在/opt/homebrew/bin
目录之下。
译者注:并不一定在此目录下,具体可以点击这里。
1 | export PATH="$PATH:/opt/homebrew/bin" |
在 Xcode 添加运行脚本以集成 SwiftLint
SwiftLint 的规则冲突
好消息是,新建的 Xcode 项目没有违反 SwiftLint 的默认规则。一旦你知道了 SwiftLint,最好从一开始就立即将其添加到每个项目中。在刚才新建的 iOS App 的Text view
后面添加一个空格。现在,编译代码时会生成警告。
1 | struct ContentView: View { |
这段代码违反了trailing_whitespace
规则,它默认是开启的。
1 | +------------------------------------------+--------+-------------+------------------------+-------------+----------+---------------+ |
Swift 正在警告在一行之后有一个额外的空格
SwiftLint 的规则
SwiftLint 包含了200多条规则,并且 Swift 社区仍在不间断的贡献更多的规则。查看 SwiftLint 规则的一种方法是在终端中运行swiftlint rules
命令(此种方式需要安装swiftlint
)。这将会显示规则以及规则的一系列属性,比如是否可选,是否可纠正。
以下是 SwiftLint 0.46.5的默认规则:
1 | swiftlint rules |
在终端运行 SwiftLint
SwiftLint 可以配置为一个仓库预提交的钩子,用以保证提交代码的风格一致。它也可以在终端中作为命令运行,只需在项目目录中运行swiftlint
即可。运行swiftlint --help
查看更多选项。
1 | swiftlint --help |
在(之前新建的项目)HelloSwiftLintApp(目录下)在终端运行swiftlint
同样会显示违反了trailing_whitespace
规则。
1 | swiftlint |
自动修复 SwiftLint 冲突
从上面的规则列表可以看出,有一些规则是可以自动修正的。这是直接针对间距规则的,就像上面介绍的额外空格一样。只要可以进行 SwiftLint 分析,就可以进行 SwiftLint 自动修正。
在终端运行swiftlint --fix
就会自动修正那些可以被自动修正的 SwiftLint 冲突。
1 | swiftlint --fix |
或者,可以将自动修复整合到 Xcode 的Build Phase
。编辑”Run Script Phase
“下的 SwiftLint 脚本。现在,在 Xcode 中编译代码时,添加尾随空格会自动删除。
1 | export PATH="$PATH:/opt/homebrew/bin" |
手动修复 SwiftLint 规则冲突
并非所有的规则冲突都可以自动修复。对于 SwiftLint 分析生成的警告以及错误,有很多种处理方式。如果只有一到两个冲突,最好的办法是修复它们,然后继续。
处理 SwiftLint 冲突的一些选项:
1 | 1. 修改代码以符合 SwiftLint 规则 |
修复冲突是最好的方法,当 SwiftLint 从项目的一开始就被整合时,这可以很容易的被实现。
下面是我写的示例代码,它遍历了 vertices 数组,创建了一条路径。使用enumerated方法生成了索引以及数据项,使用单个字符n
作为变量名会导致编译时错误,仅使用字符作为变量名会导致编译时警告。
1 | for (n, pt) in vertices.enumerated() { |
这些“标识符名称”(identifier-name)冲突不能被自动修复。为此类冲突创建一些例外可能会很有诱惑力,但是从长远来看,(此类规则)将有助于代码的可读性以及可维护性。
1 | for (index, point) in vertices.enumerated() { |
我发现关于enumerated
苹果的示例代码存在着同样的问题!
SwiftLint 的标识符冲突无法被修复
一些规则的例外情况
在某些情况下,代码需要与某些外部API或数据源兼容。Open Weather API提供了如Read JSON with codeable in Swift中所描述的 JSON 数据。这些天气数据中main
数据段含有一些有下划线的标识符,比如,feels_like
。用于 Swift 解码此 JSON 的结构体必须与 JSON 中的字段名称匹配,由于 SwiftLint 的”***identifier_name***”
规则,Swift 代码会产生编译时错误。
天气数据的示例 JSON
1 | { |
1 | struct WeatherRawData: Codable { |
不是每个(冲突)都需要被抛出。在这种情况下,可以在出现问题的代码之前简单地禁用 SwiftLint 规则,然后重新启用该规则。显然,如果这些启用/禁用代码片段在代码中到处都是,那就不太好了。这种技术应该谨慎地被使用。如果发现需要在多个位置禁用同一规则,请考虑为整个项目禁用该规则。
1 | struct WeatherRawData: Codable { |
不要急于禁用规则
偶尔会有一些 SwiftLint 规则的特例,但是不要急于禁用规则。在上面的例子中,有一种更好的方法,可以使用CodingKeys
将 Swift 变量名映射到 JSON 内容。与其注释 SwiftLint 规则,不如使用属性名feelsLike
并指定feels_like
的可选值来匹配JSON数据。
1 | // swiftlint:disable identifier_name |
1 | struct MainData: Codable { |
使用 CodingKeys 来映射 JSON 变量好于禁用 SwiftLint 规则
自定义 SwiftLint 规则
如果将 SwiftLint 添加到显示数百个问题的现有项目中,“修复所有冲突”的方法可能非常困难。在这种情况下,将 SwiftLint 配置添加到项目中可能更合适。这是一个YAML文件,在该文件中可以禁用规则,列出选择开启的规则,或者将规则仅限于此文件中的规则。这样, SwiftLint 就可以无限定制。有关更多详细信息,请参阅SwiftLint配置部分。
警告的一个例子是代码中存在 TODO 注释。SwiftLint 将这些 TODO 标记为警告,以表示这些地方还有未完成的工作。
TODO 注释被 SwiftLint 默认编译成一个警告
很多时候你既想合并代码时保留这些 TODO,也希望在编译时没有这些警告。可以在每个单独的TODO
注释前面加disable/enable
,也可以在.swiftlint.yml
文件中来禁用整个整个项目的此规则。将下方的.swiftlint.yml
文件添加到项目中,会允许项目编译而不生成 TODO 注释警告,其他规则不受影响。
1 | disabled_rules: # rule identifiers to exclude from running |
1 | struct ContentView: View { |
TODO 注释没有造成警告,其他规则不受影响
在已有的规则上使用 SwiftLint 最简单的方法是:
- 安装 SwiftLint
- 通过编译阶段脚本,将 SwiftLint 整合进 Xcode 项目中
- 编译以评估所有警告和错误
- 添加
.swiftlint.yml
文件,并禁用冲突数最多的规则 - 一次启用一条规则并修复代码中的问题
结论
对于任何 Swift 开发者来说,使用 SwiftLint 都是必要的。它有助于避免团队中关于代码样式的争论,以及建立代码风格的统一性。就我而言,它帮我摆脱了诸如创建单字符标识符等坏习惯。
将 SwiftLint 添加到已有的代码库可能比添加到新项目要复杂得多,因为它可能会显示数百个警告和错误。通过配置规则,并逐渐开启更多的规则,可以在现有项目中采用 SwiftLint。
SwiftLint 的自动修复冲突的能力非常强大,通过自动修复冲突可以显著解决数百个冲突的初始情况。只需要确保代码在进行大范围的自动更改之前已经纳入了版本控制,这样在出问题时就能很容易撤销。
译者的一些补充
关于 SwiftLint 的安装
安装的方式有几种,原文介绍的是使用 homebrew 安装。译者比较推荐直接使用CocoaPods:
1 | pod 'SwiftLint', '0.46.5' |
能清晰明了的指明项目使用了 SwiftLint ,同时也方便指定版本。
SwiftLint 不仅仅能帮助解决格式问题
SwiftLint 不仅仅能解决很多格式问题,它的功能还有很多。比如限制一个函数参数的个数,函数、文件最长多少行,使用更精简,更Swift 的函数等等。这能在很大程度上帮助我们写出高质量的代码。
很多团队伙伴在写代码时,一开始的函数,文件可能没那么臃肿。但是随着功能的增加,不断地往一个函数添加参数,不断修改函数的功能,不断往一个文件增加新的函数等等,各个地方开始变得臃肿。当 SwiftLint 告诉你函数参数个数超过了指定的个数,函数行数超过了最大值,文件超过了最大行数等等时,就应该认真考虑是不是该重构了。
在已有项目中添加 SwiftLint
在添加 SwiftLint 之前,最重要的是全体应该开一个简短的会。用来同步以及确定规则。让所有人充分表达意见,而不是一个人制定,有些人不赞同。将一些模棱两可的规则确定清楚之后,写入到 SwiftLint 配置当中,所有人都应该遵守。
译者的项目非常的庞大,刚添加 SwiftLint 的时候,警告加错误高达5000多了,根本改不过来。我采用的方法是分路径配置 SwiftLint。那些比较老的,不怎么会动的目录采用较为宽松的配置。新起一个目录,之后的新代码原则上都应该在此目录之下。此目录的配置会较为严格。
Hook git pre-commit
在提交之前做规则检查,如有问题,直接报错,无法提交代码。这样就不会产生新的警告和错误,慢慢的再修改之前的代码,经过1年多,整个项目基本就覆盖全了。
另外,关于项目的警告也需要管理,很多时候有些成员写代码的时候无视警告,导致项目警告几百个,而且越来越多。这就导致警告失去了它原有的作用,译者项目的警告是0,能够很轻松的定位哪个地方发出了新的警告。同时代码 Review 也非常重要,有些成员为了避免麻烦,各个地方都直接 disable 了 SwiftLint 的规则,这是不应该的。
最后,永远记得你只是在使用的只是一个工具,而不是目的。请充分衡量成本和收益。