今回は、「使うまでに初期化されていることが保証できる + defer = 最後までに絶対に値が指定される」を用いて、処理の意図をコンパイラ(処理系)に保証させたリファクタリング例です。
この記事のおすすめの読み方
- before の例を見て、その処理が何をしたいのか概要を読み取る
- after を見て 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
}
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
に与える値を必ず判断すること (指定し忘れた場合、コンパイルエラーになる)
```swift: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 では変数を宣言後、「変数の値を使用する前に初期化済みであること」が要求されます。もし、その制約が満たされていない場合、コンパイルエラーとなります。
```swift:初期化の挙動
func sample() {
let /* var */ v: Bool
// ❗️error: constant 'v' used before being initialized
print(v)
}
これを踏まえて、下記のように 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
}
おわりに
今回紹介した方法の大きな利点として、「人ではなく、処理系によって意図が保証されること」に価値があると思います。これによって、人が払うべき注意の一部を処理系に任せることができ、仲間の負担を減らせそうです。