🌟

コードの品質を向上!SwiftLintとSwiftFormatの導入と結果比較

2024/06/12に公開

お疲れ様です。Chamujiです。
今回はSwiftLintSwiftFormatの2点について調べた内容を書きたいと思います。
初めてZennを利用するので、今回は最近Qiitaで書いた記事をそのまま掲載してみようと思います。

そもそもSwiftLintSwiftFormatってなんだ?という方もいらっしゃいます。
それらを書く前にまずはコーディング規約というものについて説明をさせてください。

1. コーディング規約とは

  • 複数人でプログラミングをする際、全員で統一したソースコードを書くために定めるルールです。
  • プログラムの作り方はエンジニアごとに異なるため、他のエンジニアが書いたコードを理解するのは難しいです。
  • その結果以下のような問題が起こります。
    • 可読性低下により新規メンバーが理解できないため属人化・学習コストが高くなる
    • 保守性の低下
    • 潜在的なバグの原因
    • チーム全体の生産性の低下

これら問題を解決するために、コーディング規約を設けることが重要になります。

1-1.コーディング良い・悪い例

良い・悪いコーディングとは何か?
以下例を共有します。かなり極端な例で申し訳ありません。。。

スクリーンショット 2024-06-10 0.11.31.png

上記画像は左右とも「Hello,Jhon!」を呼び出すコードです。エラーなく実行してくれます。
しかし、コードをパッと見た時に左側と右側でどちらが読みやすいでしょうか?左側が見やすいと感じるかと思います。
その理由として、変数名、関数名、引数名に対し、役割や機能を明確に示しており、読み手がコードの目的や動作を容易に理解できるようになっています。

変数名 関数名 引数名
良い例(左側) userName greetUser() name
悪い例(右側) x f y

右側のようなコードにさせないよう自動チェック・補完してくれるツールこそSwiftLintSwiftFormatです。

コードの良い・悪いが何かわからないという方は一度リーダブルコードを読むとスッと頭に入ってくるかもしれません。

Qiitaにまとめてくださっている方がいますので共有します。
リーダブルコードの要点整理と活用法をまとめた

2. SwiftLintとは

  • Swift言語で書かれたコードをチェックする静的解析ツールです。
  • コーディングの規約チェック・カスタムルール作成・警告・エラー表示をしてくれます。
  • メンテナンスはRealm Inc.が行っているサードパーティー製ツールですが、最新のSwiftバージョンやコードスタイルにも対応しており、メンテナンスも頻繁にされています。

2-1. コーディング規約数

SwiftLintは規約が200以上存在します。Yamlファイルで管理し、プロジェクトに取り込む際は利用しないルールを選定して無効にする必要があります。
全てのルールを紹介することはできないため、詳細は以下のURLをご覧ください。
https://realm.github.io/SwiftLint/rule-directory.html

SwiftLintの全ルール一覧(Swift 4.2版)

3. SwiftFormatとは

  • Swift言語のコード自動整形ツールです。
  • インデントやスペースの自動調整、不要な改行の削除、imprt文並び替え等を行なってくれます。
  • メンテナンスはNick Lockwoodという開発者によって作成されたサードパーティー製ツールですが、最新のSwiftバージョンやコードスタイルにも対応しており、メンテナンスも頻繁にされています

3-1. フォーマット規約数

SwiftFormatは提供されている規約が70以上存在します。プロジェクトに取り込む際は利用しないルールを選定して無効にする必要があります。
全てのルールを紹介することはできないため、詳細は以下のURLをご覧ください。
https://github.com/nicklockwood/SwiftFormat/blob/main/Rules.md

4. SwiftLintとSwiftFormatの導入と実行結果

SwiftLintとSwiftFormatの導入方法と実際に試した結果について説明します。

4-1. SwfitLintの導入

1. SwiftLint インストール

brewコマンドでSwiftLintをダウンロードします。

brew install swiftlint

2. Xcodeを開いてSwiftLintを利用するプロジェクトを選択

新規作成の場合は「Create New Project...」から作成してください。
私の場合はすでに作成してある「SampleTestApp」を選択します。

スクリーンショット 2024-06-10 1.09.42.png

3. PROJECT, TARGETSと並んでるうちのTARGETS のプロジェクト名を選択

スクリーンショット 2024-06-10 1.13.17.png

4. Build Phasesを選択

スクリーンショット 2024-06-10 1.13.17.png

5. +マークを選択して New Run Script Phase

スクリーンショット 2024-06-10 1.13.17.png
スクリーンショット 2024-06-10 1.19.56.png

6. 以下のコードをRunScriptの中に記述

// パスを記載
export PATH="$PATH:/opt/homebrew/bin"

if which swiftlint > /dev/null; then
  swiftlint
  swiftlint autocorrect
else
  echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
fi

※すいません、この画像にはSwiftFormatの設定も含まれています。。。後ほどFormatで必要なフェーズなので一旦無視してください。
スクリーンショット 2024-06-10 1.24.03.png

7. User Script SandboxingをNoに変更

Xcode15からこのデフォルトがYesになっているため、ここをNoに変更。
スクリーンショット 2024-06-10 1.27.47.png

8. なんのルールを適用させるかYamlファイルに記述

プロジェクトファイル内に「.swiftLint.yml」を作成し、その中に採用するルールなどを書き込みます。
スクリーンショット 2024-06-10 1.32.08.png

yamlファイル作成コマンド

touch .swiftlint.yml

vscodeなどのエディタで作成したyamlファイルを開き、以下記述して保存。
ルール一覧はあくまで私個人の好きなようにしているため、自分好みに調整してください。以下ルール一覧です。
https://qiita.com/yamakentoc/items/1f1553cda309c21b09d6

# 無効にするルール
disabled_rules:
    #ファイルにあまりにも多くの行があってはならない。(ファイル内の処理を別ファイルに分割するなどした方が良い)
    - file_length
    #配列とDictionaryの末尾のカンマは付けるべきではない。
    - trailing_comma
    #行の末尾にスペースがあってはいけない。
    - trailing_whitespace
    #ファイルは末尾に1つの改行を持つべき。
    - trailing_newline
    #関数本体の長さを制限。
    - function_body_length
    #型が多くあってはいけない。
    - type_body_length
    #1行の長さが多くあってはいけない。
    - line_length
    #関数の定義時、パラメータが複数行にまたがっている場合は垂直方向に揃える。
    - vertical_parameter_alignment
    # 複数のクロージャーの場合でも、trailing closureを利用したいため
    - multiple_closures_with_trailing_closure
    # enumの引数を省略したいため
    - empty_enum_arguments
    # defaultルール以外にopt-inから採用するルール
    ######

# defaultルール以外にopt-inから採用するルール
opt_in_rules:
  - accessibility_label_for_image
  - accessibility_trait_for_button
  - anonymous_argument_in_multiline_closure
  - anyobject_protocol
  - array_init
  - attributes
  - balanced_xctest_lifecycle
  - capture_variable
  - closure_body_length
  - closure_end_indentation
  - closure_spacing
  - collection_alignment
  - comma_inheritance
  - conditional_returns_on_newline
  - contains_over_filter_count
  - contains_over_filter_is_empty
  - contains_over_first_not_nil
  - contains_over_range_nil_comparison
  - convenience_type
  - direct_return
  - discarded_notification_center_observer
  - discouraged_assert
  - discouraged_none_name
  - discouraged_object_literal
  - discouraged_optional_boolean
  - discouraged_optional_collection
  - empty_collection_literal
  - empty_count
  - empty_string
  - empty_xctest_method
  - enum_case_associated_values_count
  - expiring_todo
  - explicit_acl
  - explicit_enum_raw_value
  - explicit_init
  - explicit_self
  - explicit_top_level_acl
  - explicit_type_interface
  - extension_access_modifier
  - fallthrough
  - fatal_error_message
  - file_header
  - file_name
  - file_name_no_space
  - file_types_order
  - final_test_case
  - first_where
  - flatmap_over_map_reduce
  - force_unwrapping
  - function_default_parameter_at_end
  - ibinspectable_in_extension
  - identical_operands
  - implicit_return
  - implicitly_unwrapped_optional
  - indentation_width
  - inert_defer
  - joined_default_parameter
  - last_where
  - legacy_multiple
  - legacy_objc_type
  - let_var_whitespace
  - literal_expression_end_indentation
  - local_doc_comment
  - lower_acl_than_parent
  - missing_docs
  - modifier_order
  - multiline_arguments
  - multiline_arguments_brackets
  - multiline_function_chains
  - multiline_literal_brackets
  - multiline_parameters
  - multiline_parameters_brackets
  - nimble_operator
  - no_extension_access_modifier
  - no_grouping_extension
  - no_magic_numbers
  - non_overridable_class_declaration
  - nslocalizedstring_key
  - nslocalizedstring_require_bundle
  - number_separator
  - object_literal
  - one_declaration_per_file
  - operator_usage_whitespace
  - optional_enum_case_matching
  - overridden_super_call
  - override_in_extension
  - pattern_matching_keywords
  - period_spacing
  - prefer_nimble
  - prefer_self_in_static_references
  - prefer_self_type_over_type_of_self
  - prefer_zero_over_explicit_init
  - prefixed_toplevel_constant
  - private_action
  - private_outlet
  - private_subject
  - private_swiftui_state
  - prohibited_interface_builder
  - prohibited_super_call
  - quick_discouraged_call
  - quick_discouraged_focused_test
  - quick_discouraged_pending_test
  - raw_value_for_camel_cased_codable_enum
  - reduce_into
  - redundant_nil_coalescing
  - redundant_self_in_closure
  - redundant_type_annotation
  - required_deinit
  - required_enum_case
  - return_value_from_void_function
  - self_binding
  - shorthand_argument
  - shorthand_optional_binding
  - single_test_class
  - sorted_enum_cases
  - sorted_first_last
  - sorted_imports
  - static_operator
  - strict_fileprivate
  - strong_iboutlet
  - superfluous_else
  - switch_case_on_newline
  - test_case_accessibility
  - toggle_bool
  - trailing_closure
  - type_contents_order
  - typesafe_array_init
  - unavailable_function
  - unhandled_throwing_task
  - unneeded_parentheses_in_closure_argument
  - unowned_variable_capture
  - untyped_error_in_catch
  - unused_capture_list
  - unused_declaration
  - unused_import
  - vertical_parameter_alignment_on_call
  - vertical_whitespace_between_cases
  - vertical_whitespace_closing_braces
  - vertical_whitespace_opening_braces
  - weak_delegate
  - xct_specific_matcher
  - yoda_condition


# Lintを適用させる対象を指定
included:
    - SampleTestApp/

# Lint対象から除外するパス
excluded:
    - SampleTestAppTests/
    - SampleTestAppUITests/
    - Pods/
    - Carthage/

type_name:
    excluded:
    # クラス名はキャメルケースにする
      - lowerCamelClass


これでSwiftLintの導入は完了です。
Xcodeでコードを記述した際に自動でルール一覧の内容に基づいてコードチェックが入ります。

4-2. SwfitFormatの導入

1. SwiftFormatをインストール

brewコマンドでSwiftFormatをダウンロードします。

brew install swiftformat

2. XcodeでSwiftFormatを設定

Xcodeでビルド時に自動的にSwiftFormatを実行するために、Xcodeのビルドフェーズにスクリプトを追加します。
先ほどSwiftLintで追加したRunScriptの中に追加します。
SwiftFormat、SwiftLintの順番でScriptを記述してください。
コード整形⇨その後コードチェックという流れにしたいためです。

if which swiftformat >/dev/null; then
  swiftformat .
else
  echo "warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat"
fi

スクリーンショット 2024-06-10 1.24.03.png

3. SwiftFormatの設定ファイル作成

formatファイル作成コマンド

touch .swiftformat

vscodeなどのエディタで作成したformatファイルを開き、以下のように記述して保存。
ルール一覧はあくまで私個人の好きなようにしているため、自分好みに調整してください。以下ルール一覧です。
https://github.com/nicklockwood/SwiftFormat/blob/main/Rules.md#blankLinesAtEndOfScope

--swiftversion 5
--blankLinesAtStartOfScope 0 # スコープの開始時に空白行を許可しない
--blankLinesAtEndOfScope 0  # スコープの終了時に空白行を許可しない
--blankLinesBetweenScopes true #class, struct, enum, extension, protocol, function の宣言の前に空白行を入れる。
--braces same-line #波括弧を同じ行に配置します。
--consecutiveBlankLines 1 #連続する空行の数を1つに制御します。
--consecutiveSpaces 1 #コード内の連続するスペースの数を1つに制御
--duplicateImports #コード内で重複してインポートされているモジュールやライブラリを削除します
--elseOnSameLine true # else, catch, while キーワードのポジションを "同じ行" または "次の行" に調整する。
--indent 2
--isEmpty
--leadingDelimiters #行の先頭に来るデリミタ(例えばカンマやセミコロン)を一貫したスタイルに変換するための機能
--linebreakAtEndOfFile #ファイルの末尾に改行を追加する
--modifierOrder #Swift コード内のアクセス修飾子やその他の修飾子の順序を統一するための機能 "public, open, private, fileprivate, internal, static, final, class, override, required, convenience, lazy, dynamic, weak, unowned, optional, indirect, mutating, nonmutating, throws, rethrows"の順番
--numberFormatting #コード内の数値リテラルの形式を統一するための機能
--redundantBackticks #不要なバックティック (`) をコードから削除するための機能
--redundantBreak #不要な break ステートメントをコードから削除するための機能
--redundantGet #不要な get キーワードを削除するため
--redundantInit #明示的な初期化(init)が不要な場合にこれを削除
--redundantLet #冗長な let キーワードを削除
--redundantLetError #エラーハンドリングの際に冗長な let キーワードを削除
--redundantNilInit #冗長な nil 初期化を削除
--redundantObjc #不要な @objc 属性を削除する
--redundantParens #不必要な括弧(カッコ)を削除するためのもの
--redundantPattern #パターンマッチングにおいて冗長な部分を削除する
--redundantRawValues #列挙型で不要な生の値(raw value)を削除する
--redundantSelf #不要な self の使用を削除する
--redundantVoidReturnType
--semicolons #不要なセミコロン削除
--spaceAroundBraces #ブレース({ や })の周囲にスペースを追加する
--spaceAroundBrackets
--spaceAroundComments
--spaceAroundOperators
--spaceInsideBraces
--spaceInsideGenerics
--strongOutlets
--strongifiedSelf
--todos
--wraparguments beforefirst
--wrapparameters beforefirst
--allman false
--patternlet inline

# 利用しないルール一覧
--disable emptyBraces,hoistPatternLet,inebreaks,preferKeyPath,redundantReturn,spaceAroundGenerics,spaceAroundParens,spaceInsideBrackets,spaceInsideComments,spaceInsideParens,trailingClosures,trailingCommas,trailingSpace,typeSugar,unusedArguments,void

これでSwiftFormatの導入は完了です。
これで、XcodeでプロジェクトをビルドするたびにSwiftFormatが自動的に実行されるようになります。

4-3. SwiftLintとSwiftFormatの整形・解析対象プログラム作成

  • 導入したSwiftLintとSwiftFormatが正しく整形・解析をしてくれるかSwiftLintとSwfitFormatを利用したバージョンと利用していないバージョンで比較します。

  • 確認するプログラムとして以下のようなコードを記述しました。

任意で入力した値に対して消費税8%の値と消費税10%の値を出力結果を表示させる。


import SwiftUI
struct Kadai_part5_No1_View: View {
    @State var inputText = ""
    @State var tax8 = 0.0
    @State var tax10 = 0.0
var body:some View {
    VStack(spacing: 20){
    
        TextField("ここに文字を入力"、text: $inputText)
            .keyboardType (.numberPad)
        Button("計算"){}
        Text("価格:"'+ inputText)
        Text("消費税8%:\((Double (inputText)?? 0)*0.08) ")
        Text("消費税10%:\((Double (inputText)?? 0)*0.1)")
        }
    }
}
    

SwiftLint・SwiftFormat なし

ぱっと以下のようなフォーマットがぐちゃぐちゃでも問題なくビルドに成功していますね。

  • 12、13行目の=と数字の間のスペースが統一されていない
  • 15行目に大量のスペースがある

SwiftLint・SwiftFormat あり

ビルドに失敗していますね。正常にformat,lintが動いている証拠です。

  • SwiftFormatが正しく機能してファイル全体が自動整形されている
  • SwiftLintがエラー、ワーニングを出力している

SwiftLintが吐き出したエラーやワーニングを確認する

これらは全てSwiftLintで設定したルールに基づき吐き出されたエラーです。
これらの指摘通りプログラムを直しましょう。

10行目
・Type Name Violation: Type name 'Kadai_part5_No1_View' should only contain alphanumeric and other allowed characters (type_name)
・名前は英数字のみ。

10~21行目
・Explicit ACL Violation: All declarations should specify Access Control Level keywords explicitly(explicit_top_level_acl)
・すべての宣言に対してアクセス制御レベルを明示的に指定する

・Explicit Top Level ACL Violation: Top-level declarations should specify Access Control Level keywords explicitly (explicit_top_level_acl)
・トップレベルの宣言に対してアクセス制御レベルを明示的に指定する

・Explicit ACL Violation: All declarations should specify Access Control Level keywords explicitl
・アクセス修飾子を設定する

No Magic Numbers Violation: Magic numbers should be replaced by named constants (no_magic_numbers)
明示的な型を宣言する

指摘箇所を修正して再度実行した結果

エラーやワーニングはでずにビルド成功しました。
コード全体も規約に則っているのでとても綺麗ですね。

最後に

あまりSwiftのフォーマッターやlintに関して自分も理解できていなかったので自信理解を深めるためにこの記事を作成しました。
実行結果のために作ったプログラムのクラス名があまりにも酷いのでそこは反省だなと思っています。
なんやKadaipart5No1Viewて・・・・

Discussion