😬

[SwiftLint] no_empty_bodyルールについて

2024/07/23に公開

概要

SwiftLintに新しく実装された no_empty_body ルールの紹介をする。
https://github.com/realm/SwiftLint/issues/5615

SwiftLintとは

SwiftLintとは、Swiftのコード静的解析ツールである。
Linterのルールを設定できるため、複数の開発者がいるプロジェクトでも、コードベース内で一貫したコーディングルールを適用させることができる。
最初から有効になっているDefaultRuleと、必要に応じて有効にすることのできるOpt-In Ruleを合わせて、240のルールが実装されている。(2024/7/22現在)

丁寧にSwiftLintの解説をしてくれている記事

https://qiita.com/uhooi/items/bf888b53b4b8210108aa
https://zenn.dev/chikato/articles/32fc0185f9ff1b

no_empty_body Rule

どんなルール?

CodeBlockに、コードもコメントもない場合にwarningを出す。

func sampleFunc() {} //<- warning
No Empty Block Violation: Code blocks should contain at least one statement or comment (no_empty_block)
(日本語訳)コードブロックは、少なくとも一行 コードかコメントを含んでいるべきである。

CodeBlockは、SwiftSyntaxの定義にもとづいて、以下のものが該当する。

  • AccessorDeclSyntax (didSetなどの中身)
  • CatchClauseSyntax (do-catchのcatchの中身)
  • DeferStmtSyntax (deferの中身)
  • DeinitializerDeclSyntax (deinitの中身)
  • DoStmtSyntax (do-catchのdoの中身)
  • ForStmtSyntax (for-loopの中身)
  • FunctionDeclSyntax(関数の中身)
  • GuardStmtSyntax (guardの中身)
  • IfExprSyntax (ifelseの中身)
  • InitializerDeclSyntax (initの中身)
  • RepeatStmtSyntax (repeatの中身)
  • WhileStmtSyntax (whileの中身)

guardのコードブロックが空の場合は、コンパイルエラーになるため、空のguardブロックにはwarningを出さない。

ルールを実装したモチベーション

他のLinterには同様のルールが実装されている。(e.g. eslint)
Swiftでも、SonarqubeのLinter(SonarLint)では、そのルールがすでに実装されている。

Sonarqubeはコードの静的解析ツールだが、Xcodeの上で動かすことができない。
CI上などで使用されることが多いが、手元では動作させることができないので、CIを走らせたときにはじめてcode-smellに気付くことが多かった。

Xcode上で動作させるLinterとして有名なSwiftlintに同様のルールが実装されれば、code-smellにより早く気付けるようになるので嬉しい。
そのため、no_empty_bodyルールを実装した。(PRを出した)

ルールの目的

中身が空のコードブロックからは、それが意図的に空なのか実装し忘れなのかを判断できない。
そのため、読者の混乱を生み、可読性を害し、メンテナンスしにくいという問題を生じる可能性がある。

もしそれが実装のし忘れなら、warningを出すことでそれに気付くことができる。
もしそれがoverrideされることを予想して意図的に空なら、コメントでそれを説明するべきである。

OKな例

OKな例
//関数
func f() {
    /* do something */
}

// Accessor(e.g willSet)
var flag = true {
    willSet { /* do something */ }
}

// initとdeinit
class Apple {
    init() { /* do something */ }

    deinit { /* do something */ }
}

// For文
for _ in 0..<10 { /* do something */ }

// do-catch文
do {
    /* do something */
} catch {
    /* do something */
}

// defer
func f() {
    defer {
        /* do something */
    }
    print("other code")
}

// if-else文
if flag {
    /* do something */
} else {
    /* do something */
}

// repeat-while文
repeat { /* do something */ } while (flag)

// while文
while i < 10 { /* do something */ }

NGな例

NGな例
//関数
func f() {}

// Accessor(e.g willSet)
var flag = true {
    willSet {}
}

// initとdeinit
class Apple {
    init() {}

    deinit {}
}

// For文
for _ in 0..<10 {}

// do-catch文
do {
} catch {
}

// defer
func f() {
    defer {}
    print("other code")
}

// if-else文
if flag {
} else {
}

// repeat-while文
repeat { } while (flag)

// while文
while i < 10 {}

設定の方法(Configuration)

有効にする

.swiftlint.ymlに以下を記述する。

opt_in_rules: # some rules are turned off by default, so you need to opt-in
  - no_empty_block

一部のルールを無効にする

.swiftlint.ymlに以下を記述する。

opt_in_rules: # some rules are turned off by default, so you need to opt-in
  - no_empty_block
no_empty_block:
  severity: error # warningの代りに errorを出す。
  disabled: ["function_bodies", "initializer_bodies", "statement_blocks"] # list内で記述したルールを無効にする。

Refs:

Discussion