72
38

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Swift] var-forパターンを避けよう

Last updated at Posted at 2020-07-27

はじめに

var-forパターンは既存の言葉ではありません。
このアンチパターンよく見かけるので自分でvar-forパターンと勝手に命名しました。
コードレビューとかで**「これvar-forパターンだよね」**という感じで使えるかもしれません。

本記事の内容はSwiftの初心者向けです。

var-forパターンとは

以下のような**一時変数varとfor文(forEach、while、repeat-whileなども含む)**を利用したロジックです。

例題)1から10までの整数を3倍して、6の倍数のみの配列を生成

var result = [Int]()
for number in 1...10 {
    let tripleNumber =  number * 3
    if tripleNumber % 6 == 0 {
        result += [tripleNumber]
    }
}

よくあるのが、配列から特定の条件を満たした別の配列を作る時です。

var-forパターンの置き換え

たいていのvar-forパターンは以下のようにmapやcompactMap、filter、reduceなどの高階関数で置き換えられます。
※高階関数がわからない方向けの記事:イメージで理解するSwiftの高階関数(filter, map, reduce, compactMap, flatMap)

let result = (1...10)
    .map { $0 * 3 }
    .filter { $0 % 6 == 0 }

var-forパターンのデメリット

可読性が低い

読み手の頭の中を想像して、var-forパターンのロジックを日本語にしてみます。

var result = [Int]() // 要素がInt型のresultという名前の配列を定義し、空で初期化する
for number in 1...10 { // 1から10までループさせ
    let tripleNumber =  number * 3 // numberを3倍したtripleNumberという名前の定数を用意する
    if tripleNumber % 6 == 0 { // もしもtripleNumberが6で割り切れたら、
        result += [tripleNumber] // resultにtripleNumberを追加する
    } // tripleNumberが6で割り切れなければ何もしない
}

次に、高階関数を利用したパターンです。

let result = (1...10) // 1から10までの整数を
    .map { $0 * 3 } // 3倍して、
    .filter { $0 % 6 == 0 } // 6の倍数のみにした定数resultを定義

上記を比較してみます。

var-forパターン

要素がInt型のresultという名前の配列を定義し、空で初期化する、1から10までループさせ、numberを3倍したtripleNumberという名前の定数を用意する、もしもtripleNumberが6で割り切れたら、resultにtripleNumberを追加する、tripleNumberが6で割り切れなければ何もしない

高階関数パターン

1から10までの整数を3倍して、6の倍数のみにした定数resultを定義

どちらがわかりやすいでしょうか?
後者の方が例題そのものの性質をよく表現しており、より人間が読みやすく、宣言的なコーディングスタイルになっています。
(プログラミングパラダイムでいうと、前者は命令型プログラミング、後者は宣言型プログラミングと分類できます)

修正がしにくい

例題にこんな仕様変更があったらどう修正するでしょうか?

1から10までの整数を3倍して、6の倍数のみにした先頭要素2つの配列を生成

var-forパターンの場合、このような修正が思いつきますが、要素数が2個未満の場合に要素を追加するというのはあまり宣言的ではなく、result.count =< 2のような間違ったコードを書いてしまう可能性もあります。

var result = [Int]()
for number in 1...10 {
    let tripleNumber =  number * 3
    if tripleNumber % 6 == 0 && result.count < 2 {
        result += [tripleNumber]
    }
}

高階関数パターンであれば、以下のようにprefixを追加するだけです。

let result = (1...10)
    .map { $0 * 3 }
    .filter { $0 % 6 == 0 }
    .prefix(2)

バグを生みやすい

修正がしにくいにも書きましたが、可読性が悪い、変更がしづらいというのはバグを生みやすいコードです。
また、let定数でなく、var変数のように、コード内に値が変化する箇所があるというのもバグを生みやすい原因になります。

よりバグの少ないない安全なプログラムを作るには変化する箇所をできるだけ減らすことが有効です。
状態がなく、変化しないものはテストパターンが少なく済むので、バグを見逃す確率がぐっと減ります。

  • varよりもletをなるべく使う(変化させない)
  • class(参照型)よりもstruct/enum(値型)を使う(変化を伝播させない)
  • プリミティブ型よりも独自型やenumを利用する(変化する値のとりうる範囲を制限する)

まとめ

  • var-forパターンよりも高階関数を利用する
  • var-forパターンは可読性、修正しやすさ、安全さが劣る場合がある
72
38
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
72
38

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?