Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Swift リファクタリング 実践 Tips2

More than 3 years have passed since last update.

今回は、「使うまでに初期化されていることが保証できる + defer = 最後までに絶対に値が指定される」を用いて、処理の意図をコンパイラ(処理系)に保証させたリファクタリング例です。

この記事のおすすめの読み方
1. before の例を見て、その処理が何をしたいのか概要を読み取る
2. after を見て before との印象の違いをみてみる

before
var state: State = .Normal

func reload() {
    state = .Loading

    guard /* condition */ else {
        state = .Normal
        return
    }

    // do somethig

    guard /* condition */ else {
        state = .Error
        return
    }

    // do somethig

    state = .Normal
}
after
var state: State = .Normal

func reload() {
    let resultState: State

    state = .Loading
    defer { state = resultState }

    guard /* condition */ else {
        resultState = .Normal
        return
    }

    // do somethig

    guard /* condition */ else {
        resultState = .Error
        return
    }

    // do somethig

    resultState = .Normal
}

変数 state に注目してみると、「処理の開始時と終了時に state を更新する。(ただし、状況や処理の結果によって終了時に state に指定する値は異なる)」処理のようです。
before では、全体を見て処理の意味に確信が持てますが、after では、最初に処理の概要が簡潔に表現され、かつ、 コンパイラ(処理系)によって処理の意図が保証 されています。

after でコンパイラ(処理系)が保証してくれること:
* 最後に state の値を更新すること
* 終了時の state に与える値を必ず判断すること (指定し忘れた場合、コンパイルエラーになる)

after(最後に`state`の値を指定し忘れた場合)
func reload() {
    let resultState: State

    state = .Loading
    // ❗️error: constant 'resultState' used before being initialized
    defer { state = resultState }

    guard /* condition */ else {
        resultState = .Normal
        return
    }

    // do somethig

    guard /* condition */ else {
        // missing 😦
        return
    }

    // do somethig

    resultState = .Normal
}

もう少し詳しく

Swift では変数を宣言後、「変数の値を使用する前に初期化済みであること」が要求されます。もし、その制約が満たされていない場合、コンパイルエラーとなります。

初期化の挙動
func sample() {
    let /* var */ v: Bool
    // ❗️error: constant 'v' used before being initialized
    print(v)
}

これを踏まえて、下記のように defer を使うと、 「スコープを抜ける直前には必ず値が与えられている」ことが処理系によって保証 されます。
余談ですが、defer のブロック内で使用する変数は後で宣言しても良いです。

deferと変数の値
// deferの前で変数宣言
func sample() {
    let a: Int
    defer { print(a) }
    a = 1  // 指定しないと error
}

// deferの後で変数宣言
func sample() {
    defer { print(a) }
    let a: Int
    a = 1  // 指定しないと error
}

おわりに

今回紹介した方法の大きな利点として、「人ではなく、処理系によって意図が保証されること」に価値があると思います。これによって、人が払うべき注意の一部を処理系に任せることができ、仲間の負担を減らせそうです。

前回 (Swift リファクタリング 実践 Tips1)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away