用 SwiftLint 保持 Swift 風格一致

語言: CN / TW / HK

前言

程式碼風格可能是一個有爭議的話題,並且在開發人員之間引發一些激烈的討論。使用工具強制執行一套程式碼風格規則對於避免一些爭論,以及確保在整個專案中保持程式碼風格的一致性非常有幫助。SwiftLint 可以很容易的整合進 Xcode 專案中,以便在編譯時將程式碼風格衝突標記為警告或者錯誤。


使用 Xcode 整合 SwiftLint

你可以在 Github上 獲得SwiftLint。它可以使用多種方式安裝,比如,直接下載 SwiftLint.pkg 包,或者使用HomeBrew命令列。

bash brew install swiftlint

安裝 SwiftLint 後,可以通過在主 app target 的Build Phase(構築階段)下新增一個Run Phase(執行階段)的執行指令碼,來整合進 Xcode 專案。點選+號按鈕,選擇"New Run Script Phase",新增下面的指令碼。在 silicon Macs (搭載M1晶片)上需要新增export語句,因為HomeBrew的二進位制檔案預設安裝在/opt/homebrew/bin目錄之下。

譯者注:並不一定在此目錄下,具體可以點選這裡

bash export PATH="$PATH:/opt/homebrew/bin" if which swiftlint > /dev/null; then swiftlint else echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" fi

在Xcode新增執行指令碼以整合SwiftLint

在 Xcode 新增執行指令碼以整合 SwiftLint


SwiftLint 的規則衝突

好訊息是,新建的 Xcode 專案沒有違反 SwiftLint 的預設規則。一旦你知道了 SwiftLint,最好從一開始就立即將其新增到每個專案中。在剛才新建的 iOS App 的Text view後面新增一個空格。現在,編譯程式碼時會生成警告。

```swift struct ContentView: View { var body: some View { VStack { Text("Hello, world!")

        Text("second line")

        Spacer()
    }
}

} ```

這段程式碼違反了trailing_whitespace規則,它預設是開啟的。

bash +------------------------------------------+--------+-------------+------------------------+-------------+----------+---------------+ | identifier | opt-in | correctable | enabled in your config | kind | analyzer | configuration | +------------------------------------------+--------+-------------+------------------------+-------------+----------+---------------+ | trailing_whitespace | no | yes | yes | style | no | warning, i... |

Swift正在警告在一行之後有一個額外的空格

Swift 正在警告在一行之後有一個額外的空格


SwiftLint 的規則

SwiftLint 包含了200多條規則,並且 Swift 社群仍在不間斷的貢獻更多的規則。檢視 SwiftLint 規則的一種方法是在終端中執行swiftlint rules命令(此種方式需要安裝swiftlint)。這將會顯示規則以及規則的一系列屬性,比如是否可選,是否可糾正。

以下是 SwiftLint 0.46.5的預設規則:

bash swiftlint rules +------------------------------------------+--------+-------------+------------------------+-------------+----------+---------------+ | identifier | opt-in | correctable | enabled in your config | kind | analyzer | configuration | +------------------------------------------+--------+-------------+------------------------+-------------+----------+---------------+ | anonymous_argument_in_multiline_closure | yes | no | no | idiomatic | no | warning | | anyobject_protocol | yes | yes | no | lint | no | warning | | array_init | yes | no | no | lint | no | warning | | attributes | yes | no | no | style | no | warning, a... | | balanced_xctest_lifecycle | yes | no | no | lint | no | warning | | block_based_kvo | no | no | yes | idiomatic | no | warning | | capture_variable | yes | no | no | lint | yes | warning | | class_delegate_protocol | no | no | yes | lint | no | warning | | closing_brace | no | yes | yes | style | no | warning | | closure_body_length | yes | no | no | metrics | no | warning: 2... | | closure_end_indentation | yes | yes | no | style | no | warning | | closure_parameter_position | no | no | yes | style | no | warning | | closure_spacing | yes | yes | no | style | no | warning | | collection_alignment | yes | no | no | style | no | warning, a... | | colon | no | yes | yes | style | no | warning, f... | | comma | no | yes | yes | style | no | warning | | comment_spacing | no | yes | yes | lint | no | warning | | compiler_protocol_init | no | no | yes | lint | no | warning | | computed_accessors_order | no | no | yes | style | no | warning, o... | | conditional_returns_on_newline | yes | no | no | style | no | warning, i... | | contains_over_filter_count | yes | no | no | performance | no | warning | | contains_over_filter_is_empty | yes | no | no | performance | no | warning | | contains_over_first_not_nil | yes | no | no | performance | no | warning | | contains_over_range_nil_comparison | yes | no | no | performance | no | warning | | control_statement | no | yes | yes | style | no | warning | | convenience_type | yes | no | no | idiomatic | no | warning | | custom_rules | no | no | no | style | no | user-defin... | | cyclomatic_complexity | no | no | yes | metrics | no | warning: 1... | | deployment_target | no | no | yes | lint | no | warning, i... | | discarded_notification_center_observer | yes | no | no | lint | no | warning | | discouraged_assert | yes | no | no | idiomatic | no | warning | | discouraged_direct_init | no | no | yes | lint | no | warning, t... | | discouraged_none_name | yes | no | no | idiomatic | no | warning | | discouraged_object_literal | yes | no | no | idiomatic | no | warning, i... | | discouraged_optional_boolean | yes | no | no | idiomatic | no | warning | | discouraged_optional_collection | yes | no | no | idiomatic | no | warning | | duplicate_enum_cases | no | no | yes | lint | no | error | | duplicate_imports | no | no | yes | idiomatic | no | warning | | duplicated_key_in_dictionary_literal | no | no | yes | lint | no | warning | | dynamic_inline | no | no | yes | lint | no | error | | empty_collection_literal | yes | no | no | performance | no | warning | | empty_count | yes | no | no | performance | no | error, onl... | | empty_enum_arguments | no | yes | yes | style | no | warning | | empty_parameters | no | yes | yes | style | no | warning | | empty_parentheses_with_trailing_closure | no | yes | yes | style | no | warning | | empty_string | yes | no | no | performance | no | warning | | empty_xctest_method | yes | no | no | lint | no | warning | | enum_case_associated_values_count | yes | no | no | metrics | no | warning: 5... | | expiring_todo | yes | no | no | lint | no | (approachi... | | explicit_acl | yes | no | no | idiomatic | no | warning | | explicit_enum_raw_value | yes | no | no | idiomatic | no | warning | | explicit_init | yes | yes | no | idiomatic | no | warning | | explicit_self | yes | yes | no | style | yes | warning | | explicit_top_level_acl | yes | no | no | idiomatic | no | warning | | explicit_type_interface | yes | no | no | idiomatic | no | warning, e... | | extension_access_modifier | yes | no | no | idiomatic | no | warning | | fallthrough | yes | no | no | idiomatic | no | warning | | fatal_error_message | yes | no | no | idiomatic | no | warning | | file_header | yes | no | no | style | no | warning, r... | | file_length | no | no | yes | metrics | no | warning: 4... | | file_name | yes | no | no | idiomatic | no | (severity)... | | file_name_no_space | yes | no | no | idiomatic | no | (severity)... | | file_types_order | yes | no | no | style | no | warning, o... | | first_where | yes | no | no | performance | no | warning | | flatmap_over_map_reduce | yes | no | no | performance | no | warning | | for_where | no | no | yes | idiomatic | no | warning | | force_cast | no | no | yes | idiomatic | no | error | | force_try | no | no | yes | idiomatic | no | error | | force_unwrapping | yes | no | no | idiomatic | no | warning | | function_body_length | no | no | yes | metrics | no | warning: 4... | | function_default_parameter_at_end | yes | no | no | idiomatic | no | warning | | function_parameter_count | no | no | yes | metrics | no | warning: 5... | | generic_type_name | no | no | yes | idiomatic | no | (min_lengt... | | ibinspectable_in_extension | yes | no | no | lint | no | warning | | identical_operands | yes | no | no | lint | no | warning | | identifier_name | no | no | yes | style | no | (min_lengt... | | implicit_getter | no | no | yes | style | no | warning | | implicit_return | yes | yes | no | style | no | warning, i... | | implicitly_unwrapped_optional | yes | no | no | idiomatic | no | warning, m... | | inclusive_language | no | no | yes | style | no | warning, a... | | indentation_width | yes | no | no | style | no | severity: ... | | inert_defer | no | no | yes | lint | no | warning | | is_disjoint | no | no | yes | idiomatic | no | warning | | joined_default_parameter | yes | yes | no | idiomatic | no | warning | | large_tuple | no | no | yes | metrics | no | warning: 2... | | last_where | yes | no | no | performance | no | warning | | leading_whitespace | no | yes | yes | style | no | warning | | legacy_cggeometry_functions | no | yes | yes | idiomatic | no | warning | | legacy_constant | no | yes | yes | idiomatic | no | warning | | legacy_constructor | no | yes | yes | idiomatic | no | warning | | legacy_hashing | no | no | yes | idiomatic | no | warning | | legacy_multiple | yes | no | no | idiomatic | no | warning | | legacy_nsgeometry_functions | no | yes | yes | idiomatic | no | warning | | legacy_objc_type | yes | no | no | idiomatic | no | warning | | legacy_random | yes | no | no | idiomatic | no | warning | | let_var_whitespace | yes | no | no | style | no | warning | | line_length | no | no | yes | metrics | no | warning: 1... | | literal_expression_end_indentation | yes | yes | no | style | no | warning | | lower_acl_than_parent | yes | no | no | lint | no | warning | | mark | no | yes | yes | lint | no | warning | | missing_docs | yes | no | no | lint | no | warning: o... | | modifier_order | yes | yes | no | style | no | warning, p... | | multiline_arguments | yes | no | no | style | no | warning, f... | | multiline_arguments_brackets | yes | no | no | style | no | warning | | multiline_function_chains | yes | no | no | style | no | warning | | multiline_literal_brackets | yes | no | no | style | no | warning | | multiline_parameters | yes | no | no | style | no | warning, a... | | multiline_parameters_brackets | yes | no | no | style | no | warning | | multiple_closures_with_trailing_closure | no | no | yes | style | no | warning | | nesting | no | no | yes | metrics | no | (type_leve... | | nimble_operator | yes | yes | no | idiomatic | no | warning | | no_extension_access_modifier | yes | no | no | idiomatic | no | error | | no_fallthrough_only | no | no | yes | idiomatic | no | warning | | no_grouping_extension | yes | no | no | idiomatic | no | warning | | no_space_in_method_call | no | yes | yes | style | no | warning | | notification_center_detachment | no | no | yes | lint | no | warning | | nslocalizedstring_key | yes | no | no | lint | no | warning | | nslocalizedstring_require_bundle | yes | no | no | lint | no | warning | | nsobject_prefer_isequal | no | no | yes | lint | no | warning | | number_separator | yes | yes | no | style | no | warning, m... | | object_literal | yes | no | no | idiomatic | no | warning, i... | | opening_brace | no | yes | yes | style | no | warning, a... | | operator_usage_whitespace | yes | yes | no | style | no | warning, l... | | operator_whitespace | no | no | yes | style | no | warning | | optional_enum_case_matching | yes | yes | no | style | no | warning | | orphaned_doc_comment | no | no | yes | lint | no | warning | | overridden_super_call | yes | no | no | lint | no | warning, e... | | override_in_extension | yes | no | no | lint | no | warning | | pattern_matching_keywords | yes | no | no | idiomatic | no | warning | | prefer_nimble | yes | no | no | idiomatic | no | warning | | prefer_self_in_static_references | yes | yes | no | style | no | N/A | | prefer_self_type_over_type_of_self | yes | yes | no | style | no | warning | | prefer_zero_over_explicit_init | yes | yes | no | idiomatic | no | warning | | prefixed_toplevel_constant | yes | no | no | style | no | warning, o... | | private_action | yes | no | no | lint | no | warning | | private_outlet | yes | no | no | lint | no | warning, a... | | private_over_fileprivate | no | yes | yes | idiomatic | no | warning, v... | | private_subject | yes | no | no | lint | no | warning | | private_unit_test | no | no | yes | lint | no | warning: X... | | prohibited_interface_builder | yes | no | no | lint | no | warning | | prohibited_super_call | yes | no | no | lint | no | warning, e... | | protocol_property_accessors_order | no | yes | yes | style | no | warning | | quick_discouraged_call | yes | no | no | lint | no | warning | | quick_discouraged_focused_test | yes | no | no | lint | no | warning | | quick_discouraged_pending_test | yes | no | no | lint | no | warning | | raw_value_for_camel_cased_codable_enum | yes | no | no | lint | no | warning | | reduce_boolean | no | no | yes | performance | no | warning | | reduce_into | yes | no | no | performance | no | warning | | redundant_discardable_let | no | yes | yes | style | no | warning | | redundant_nil_coalescing | yes | yes | no | idiomatic | no | warning | | redundant_objc_attribute | no | yes | yes | idiomatic | no | warning | | redundant_optional_initialization | no | yes | yes | idiomatic | no | warning | | redundant_set_access_control | no | no | yes | idiomatic | no | warning | | redundant_string_enum_value | no | no | yes | idiomatic | no | warning | | redundant_type_annotation | yes | yes | no | idiomatic | no | warning | | redundant_void_return | no | yes | yes | idiomatic | no | warning | | required_deinit | yes | no | no | lint | no | warning | | required_enum_case | yes | no | no | lint | no | No protoco... | | return_arrow_whitespace | no | yes | yes | style | no | warning | | self_in_property_initialization | no | no | yes | lint | no | warning | | shorthand_operator | no | no | yes | style | no | error | | single_test_class | yes | no | no | style | no | warning | | sorted_first_last | yes | no | no | performance | no | warning | | sorted_imports | yes | yes | no | style | no | warning | | statement_position | no | yes | yes | style | no | (statement... | | static_operator | yes | no | no | idiomatic | no | warning | | strict_fileprivate | yes | no | no | idiomatic | no | warning | | strong_iboutlet | yes | yes | no | lint | no | warning | | superfluous_disable_command | no | no | yes | lint | no | warning | | switch_case_alignment | no | no | yes | style | no | warning, i... | | switch_case_on_newline | yes | no | no | style | no | warning | | syntactic_sugar | no | yes | yes | idiomatic | no | warning | | test_case_accessibility | yes | yes | no | lint | no | warning, a... | | todo | no | no | yes | lint | no | warning | | toggle_bool | yes | yes | no | idiomatic | no | warning | | trailing_closure | yes | no | no | style | no | warning, o... | | trailing_comma | no | yes | yes | style | no | warning, m... | | trailing_newline | no | yes | yes | style | no | warning | | trailing_semicolon | no | yes | yes | idiomatic | no | warning | | trailing_whitespace | no | yes | yes | style | no | warning, i... | | type_body_length | no | no | yes | metrics | no | warning: 2... | | type_contents_order | yes | no | no | style | no | warning, o... | | type_name | no | no | yes | idiomatic | no | (min_lengt... | | unavailable_function | yes | no | no | idiomatic | no | warning | | unneeded_break_in_switch | no | no | yes | idiomatic | no | warning | | unneeded_parentheses_in_closure_argument | yes | yes | no | style | no | warning | | unowned_variable_capture | yes | no | no | lint | no | warning | | untyped_error_in_catch | yes | yes | no | idiomatic | no | warning | | unused_capture_list | no | no | yes | lint | no | warning | | unused_closure_parameter | no | yes | yes | lint | no | warning | | unused_control_flow_label | no | yes | yes | lint | no | warning | | unused_declaration | yes | no | no | lint | yes | severity: ... | | unused_enumerated | no | no | yes | idiomatic | no | warning | | unused_import | yes | yes | no | lint | yes | severity: ... | | unused_optional_binding | no | no | yes | style | no | warning, i... | | unused_setter_value | no | no | yes | lint | no | warning | | valid_ibinspectable | no | no | yes | lint | no | warning | | vertical_parameter_alignment | no | no | yes | style | no | warning | | vertical_parameter_alignment_on_call | yes | no | no | style | no | warning | | vertical_whitespace | no | yes | yes | style | no | warning, m... | | vertical_whitespace_between_cases | yes | yes | no | style | no | warning | | vertical_whitespace_closing_braces | yes | yes | no | style | no | N/A | | vertical_whitespace_opening_braces | yes | yes | no | style | no | N/A | | void_return | no | yes | yes | style | no | warning | | weak_delegate | yes | yes | no | lint | no | warning | | xct_specific_matcher | yes | no | no | idiomatic | no | warning | | xctfail_message | no | no | yes | idiomatic | no | warning | | yoda_condition | yes | no | no | lint | no | warning | +------------------------------------------+--------+-------------+------------------------+-------------+----------+---------------+


在終端執行 SwiftLint

SwiftLint 可以配置為一個倉庫預提交的鉤子,用以保證提交程式碼的風格一致。它也可以在終端中作為命令執行,只需在專案目錄中執行swiftlint即可。執行swiftlint --help檢視更多選項。

```bash swiftlint --help OVERVIEW: A tool to enforce Swift style and conventions.

USAGE: swiftlint

OPTIONS: --version Show the version. -h, --help Show help information.

SUBCOMMANDS: analyze Run analysis rules docs Open SwiftLint documentation website in the default web browser generate-docs Generates markdown documentation for all rules lint (default) Print lint warnings and errors rules Display the list of rules and their identifiers version Display the current version of SwiftLint

See 'swiftlint help ' for detailed help. ```

在(之前新建的專案)HelloSwiftLintApp(目錄下)在終端執行swiftlint同樣會顯示違反了trailing_whitespace規則。

bash swiftlint Linting Swift files in current working directory Linting 'HelloSwiftLintApp.swift' (1/2) Linting 'ContentView.swift' (2/2) ...HelloSwiftLint/ContentView.swift:13:1: warning: Trailing Whitespace Violation: Lines should not have trailing whitespace. (trailing_whitespace) Done linting! Found 1 violation, 0 serious in 2 files.


自動修復 SwiftLint 衝突

從上面的規則列表可以看出,有一些規則是可以自動修正的。這是直接針對間距規則的,就像上面介紹的額外空格一樣。只要可以進行 SwiftLint 分析,就可以進行 SwiftLint 自動修正。

在終端執行swiftlint --fix就會自動修正那些可以被自動修正的 SwiftLint 衝突。

bash swiftlint --fix Correcting Swift files in current working directory Correcting 'HelloSwiftLintApp.swift' (1/2) Correcting 'ContentView.swift' (2/2) .../HelloSwiftLint/ContentView.swift:13:1 Corrected Trailing Whitespace Done inspecting 2 files for auto-correction!

或者,可以將自動修復整合到 Xcode 的Build Phase。編輯"Run Script Phase"下的 SwiftLint 指令碼。現在,在 Xcode 中編譯程式碼時,新增尾隨空格會自動刪除。

bash export PATH="$PATH:/opt/homebrew/bin" if which swiftlint > /dev/null; then swiftlint --fix && swiftlint else echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" fi


手動修復 SwiftLint 規則衝突

並非所有的規則衝突都可以自動修復。對於 SwiftLint 分析生成的警告以及錯誤,有很多種處理方式。如果只有一到兩個衝突,最好的辦法是修復它們,然後繼續。

處理 SwiftLint 衝突的一些選項:

bash 1. 修改程式碼以符合 SwiftLint 規則 2. 在程式碼中新增特例,以忽略特定的規則衝突 3. 為專案定製 SwiftLint 規則 4. 忽略這些警告 -- 這不是一個好的選項

修復衝突是最好的方法,當 SwiftLint 從專案的一開始就被整合時,這可以很容易的被實現。

下面是我寫的示例程式碼,它遍歷了 vertices 陣列,建立了一條路徑。使用enumerated方法生成了索引以及資料項,使用單個字元n作為變數名會導致編譯時錯誤,僅使用字元作為變數名會導致編譯時警告。

swift for (n, pt) in vertices.enumerated() { n == 0 ? path.move(to: pt) : path.addLine(to: pt) }

這些“識別符號名稱”(identifier-name衝突不能被自動修復。為此類衝突建立一些例外可能會很有誘惑力,但是從長遠來看,(此類規則)將有助於程式碼的可讀性以及可維護性。

swift for (index, point) in vertices.enumerated() { index == 0 ? path.move(to: point) : path.addLine(to: point) }

我發現關於enumerated蘋果的示例程式碼存在著同樣的問題!

SwiftLint的識別符號衝突無法被修復

SwiftLint 的識別符號衝突無法被修復


一些規則的例外情況

在某些情況下,程式碼需要與某些外部API或資料來源相容。Open Weather API提供瞭如Read JSON with codeable in Swift中所描述的 JSON 資料。這些天氣資料中main資料段含有一些有下劃線的識別符號,比如,feels_like 。用於 Swift 解碼此 JSON 的結構體必須與 JSON 中的欄位名稱匹配,由於 SwiftLint 的”***identifier_name***”規則,Swift 程式碼會產生編譯時錯誤。

天氣資料的示例 JSON

json { "coord": { "lon": -122.08, "lat": 37.39 }, "weather": [ { "id": 800, "main": "Clear", "description": "clear sky", "icon": "01d" } ], "base": "stations", "main": { "temp": 9.4, "feels_like": 8.71, "temp_min": 7.22, "temp_max": 11.11, "pressure": 1023, "humidity": 100, "sea_level": 100 }, "visibility": 16093, "wind": { "speed": 1.5, "deg": 350 }, "clouds": { "all": 1 }, "dt": 1560350645, "sys": { "type": 1, "id": 5122, "message": 0.0139, "country": "US", "sunrise": 1560343627, "sunset": 1560396563 }, "timezone": -25200, "id": 420006353, "name": "Mountain View", "cod": 200 }

```swift struct WeatherRawData: Codable { var name: String var timezone: Double var weather: [WeatherData] var main: MainData var wind: WindData var clouds: CloudsData

struct WeatherData: Codable {
    var id: Double
    var main: String
    var description: String
    var icon: String
}

struct MainData: Codable {
    var temp: Double
    var feels_like: Double
    var temp_min: Double
    var temp_max: Double
    var pressure: Double
    var humidity: Double
}

struct WindData: Codable {
    var speed: Double
    var deg: Double
}

struct CloudsData: Codable {
    var all: Double
}

} ```

不是每個(衝突)都需要被丟擲。在這種情況下,可以在出現問題的程式碼之前簡單地禁用 SwiftLint 規則,然後重新啟用該規則。顯然,如果這些啟用/禁用程式碼片段在程式碼中到處都是,那就不太好了。這種技術應該謹慎地被使用。如果發現需要在多個位置禁用同一規則,請考慮為整個專案禁用該規則。

```swift struct WeatherRawData: Codable { var name: String var timezone: Double var weather: [WeatherData] var main: MainData var wind: WindData var clouds: CloudsData

struct WeatherData: Codable {
    var id: Double
    var main: String
    var description: String
    var icon: String
}

// swiftlint:disable identifier_name
struct MainData: Codable {
    var temp: Double
    var feels_like: Double
    var temp_min: Double
    var temp_max: Double
    var pressure: Double
    var humidity: Double
}
// swiftlint:enable identifier_name

struct WindData: Codable {
    var speed: Double
    var deg: Double
}

struct CloudsData: Codable {
    var all: Double
}

} ```


不要急於禁用規則

偶爾會有一些 SwiftLint 規則的特例,但是不要急於禁用規則。在上面的例子中,有一種更好的方法,可以使用CodingKeys將 Swift 變數名對映到 JSON 內容。與其註釋 SwiftLint 規則,不如使用屬性名feelsLike並指定feels_like的可選值來匹配JSON資料。

swift // swiftlint:disable identifier_name struct MainData: Codable { var temp: Double var feels_like: Double var temp_min: Double var temp_max: Double var pressure: Double var humidity: Double } // swiftlint:enable identifier_name

```swift struct MainData: Codable { let temp: Double let feelsLike: Double let tempMin: Double let tempMax: Double let pressure: Double let humidity: Double

enum CodingKeys: String, CodingKey {
    case temp
    case feelsLike = "feels_like"
    case tempMin = "temp_min"
    case tempMax = "temp_max"
    case pressure
    case humidity
}

} ```

使用CodingKeys來對映JSON變數好於禁用SwiftLint規則

使用 CodingKeys 來對映 JSON 變數好於禁用 SwiftLint 規則


自定義 SwiftLint 規則

如果將 SwiftLint 新增到顯示數百個問題的現有專案中,“修復所有衝突”的方法可能非常困難。在這種情況下,將 SwiftLint 配置新增到專案中可能更合適。這是一個YAML檔案,在該檔案中可以禁用規則,列出選擇開啟的規則,或者將規則僅限於此檔案中的規則。這樣, SwiftLint 就可以無限定製。有關更多詳細資訊,請參閱SwiftLint配置部分。

警告的一個例子是程式碼中存在 TODO 註釋。SwiftLint 將這些 TODO 標記為警告,以表示這些地方還有未完成的工作。

TODO註釋被SwiftLint預設編譯成一個警告

TODO 註釋被 SwiftLint 預設編譯成一個警告

很多時候你既想合併程式碼時保留這些 TODO,也希望在編譯時沒有這些警告。可以在每個單獨的TODO註釋前面加disable/enable,也可以在.swiftlint.yml檔案中來禁用整個整個專案的此規則。將下方的.swiftlint.yml檔案新增到專案中,會允許專案編譯而不生成 TODO 註釋警告,其他規則不受影響。

yaml disabled_rules: # rule identifiers to exclude from running - todo

```swift struct ContentView: View {

var body: some View {
    VStack {
        Text("Hello, world!")

        // TODO: Remove this function when done

        let a = 24
        Text("second line \(a)")

        Spacer()
    }
}

} ```

TODO註釋沒有造成警告,其他規則不受影響

TODO 註釋沒有造成警告,其他規則不受影響

在已有的規則上使用 SwiftLint 最簡單的方法是:

  1. 安裝 SwiftLint
  2. 通過編譯階段指令碼,將 SwiftLint 整合進 Xcode 專案中
  3. 編譯以評估所有警告和錯誤
  4. 新增.swiftlint.yml檔案,並禁用衝突數最多的規則
  5. 一次啟用一條規則並修復程式碼中的問題

結論

對於任何 Swift 開發者來說,使用 SwiftLint 都是必要的。它有助於避免團隊中關於程式碼樣式的爭論,以及建立程式碼風格的統一性。就我而言,它幫我擺脫了諸如建立單字元識別符號等壞習慣。

將 SwiftLint 新增到已有的程式碼庫可能比新增到新專案要複雜得多,因為它可能會顯示數百個警告和錯誤。通過配置規則,並逐漸開啟更多的規則,可以在現有專案中採用 SwiftLint。

SwiftLint 的自動修復衝突的能力非常強大,通過自動修復衝突可以顯著解決數百個衝突的初始情況。只需要確保程式碼在進行大範圍的自動更改之前已經納入了版本控制,這樣在出問題時就能很容易撤銷。


譯者的一些補充

關於 SwiftLint 的安裝

安裝的方式有幾種,原文介紹的是使用 homebrew 安裝。譯者比較推薦直接使用CocoaPods:

swift 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 的規則,這是不應該的。

最後,永遠記得你只是在使用的只是一個工具,而不是目的。請充分衡量成本和收益。

我正在參與掘金技術社群創作者簽約計劃招募活動,點選連結報名投稿