guardとifを使い分けるタイミングについて、指針を整理しました。
結論
- 正常系はifがベター
- 異常系はguardがベター
guardを用いて二重否定や複数条件の分岐を記述すると可読性が落ちるので、
最終的には状況判断。
正常系・異常系 定義
ここで、正常系とは、システムやプログラムが期待通りに動作している状態を示します。
異常系とは、期待される動作をしない状態を指します。
guardの特徴
guardの特徴として、elseの場合は関数のスコープから退出すること(rerurnやthrow等)を強制される点があります。
数字を受け取って、0だった場合エラーを返す処理を考えてみましょう。
ifを使用する場合以下になります。
if value == 0 {
// エラー処理の実行
throw CalculationError.dividingByZero
}
// 後続処理は実行されない
ここで、仮にthrowを忘れたとしても、ifの場合は後続処理に向かってしまいます。
if value == 0 {
// エラー処理を書き忘れる
}
// 後続処理が実行される(本来はエラー発生時点で処理を止めたい)
guardを使用している場合、エラー処理やreturnを書かないとエラーが発生します。
// guardを使用する場合
// valueは0でない状況が正しい
guard value != 0 else {
// エラー処理
// throw部分をコメントアウトするとエラーが発生する
throw CalculationError.dividingByZero
}
// 後続処理
guardを使用することで、elseになった時点で処理を終わらせる。エラーを先に投げるということを保証できます。要するに、条件成立している場合後続処理へいくことが、コンパイラにより保証されるということを示しています。
また、細かい話になりますが、guardを使用する場合、後続処理を関数内のルートのインデントとして書くことが実現します。ifの場合はネストが必要になりますね。
この点がif分岐との最大の違いです。
guardは可読性に難がある?
guardは可読性が良くないから、積極的に使わない。使用してもオプショナル型をアンラップする時くらいだという意見もしばしば聞きます。
そう言う方は`else`の場合の処理に注目してしまっているのかなと想像しています。
guardの本質は、「真であるべき処理」です。
言葉通り、防衛(guard)するブロックのようなイメージで捉え、意識を後続処理に向けると、また違った感覚で向き合えるのではないかと想像しています。
guard 条件 else { … }
/// 以降guard節の条件をクリアした場合の処理。
/// else以降は異常系の処理なので、注目しない。条件が真だった場合の後続処理に意識を向ける。
guardに併用する形で二重否定や複雑な条件式を使用すると、可読性に難が生じます。
こうした状況では、説明変数を設置すると可読性を担保できます。
// before
guard a.isEmpty || !b.isEmpty else { return }
// after
let eitherAEmptyOrBHasElements: Bool = a.isEmpty || !b.isEmpty
guard eitherAEmptyOrBHasElements else { return }
一方で、こうしたアプローチは冗長なコードを生み出します。一長一短ですね。
ここまで読んでくださりありがとうございました。