今回は、If文の入れ子を避けるためのテクニックについて説明する。
よくIf文が何重にも入れ子になったプログラムを見かけるけれど、あまり入れ子が深くなると複雑になってしまう。
たとえば以下のようなコードである。
Sub Ifのネスト() If a = b Then If c = d Then If e = f Then If g = h Then MsgBox "OK" End If End If End If End If End Sub
Andで条件をまとめられるなら問題ないが、そうもいかないケースというのもある。たとえばファイルの存在を調べるIf文の中にファイル名をチェックするIf文を入れ子にするようなケースである。
ファイルが無いのにファイル名を参照しようとするとエラーになるので、これはまずファイルの存在をチェックした後、そのファイルが存在していた場合だけファイル名のチェックを行うというふうに順序を守る必要がある。
Andは両側の条件を評価してしまうので、順序性のある処理には使えない。
さて、今回はどんどん増えていくIfのネストを解消するためのテクニックについて紹介したい。
早期リターン
まず他言語でよく知られているのは早期リターンである。
Cなどの言語では「return;」を書くことでその時点で関数を終わらせることができる。これを活用したのが早期リターンである。
VBAの場合はreturn文ではないので本来は早期イグジットと呼ぶべきかもしれないが、今回は一般的な呼称である「早期リターン」を採用する。
以下は冒頭で紹介したコードを早期リターンで書き直したものだ。
Sub 早期リターン版() If Not a = b Then Exit Sub If Not c = d Then Exit Sub If Not e = f Then Exit Sub If Not g = h Then Exit Sub MsgBox "OK" End Sub
冒頭のコードはa=bであれば次に進む、c=dであれば次に進むというものであるが、裏返せば「そうでなければ終了」ということになる。この観点で書き直したのが上記のコードである。
a=bでなければ終了、c=dでなければ終了。
最終的にどの条件にも引っかからなければOKメッセージが表示される。
やっていることは冒頭のコードと変わらないが、ネストが減ってシンプルになっている。
次にループの中にIf文がネストしているケースを考えてみよう。
Sub ループとIfのネスト() For i = 1 To 10 If a = b Then If c = d Then If e = f Then If g = h Then MsgBox "OK" End If End If End If End If Next End Sub
先ほどと同じように早期リターンで書き直したいところだが、ループの場合は終了ではなく次の周回に移る必要があるのでExitが使えない。
つまり、以下のように直接早期リターンで書いてしまうと、一回目にNGが出た時点でプロシージャが終了してしまうため、きちんと10回ループされないのだ。
Sub ループと早期リターン_NG() For i = 1 To 10 If Not a = b Then Exit Sub If Not c = d Then Exit Sub If Not e = f Then Exit Sub If Not g = h Then Exit Sub MsgBox "OK" Next End Sub
こういう場合はループ内の判定部分を別のプロシージャにしてしまうことで早期リターンが使えるようになる。
具体的には、このように書く。
Sub ループと早期リターン() For i = 1 To 10 Call 別プロシージャで早期リターン(a, b, c, d, e, f, g, h) Next End Sub Sub 別プロシージャで早期リターン(a, b, c, d, e, f, g, h) If Not a = b Then Exit Sub If Not c = d Then Exit Sub If Not e = f Then Exit Sub If Not g = h Then Exit Sub MsgBox "OK" End Sub
このように、一見早期リターンテクニックの適用が難しい場面でも別プロシージャ化することで適用可能になるケースも多い。
疑似コンティニュー
さて、C言語などには、ループの途中で内容をすっとばして次の周回に入るためのContinue命令がある。
これをつかえばサブルーチン化しなくとも早期リターンと同じような効果が得られる。なんて羨ましい。
VBAにContinue命令はないが、GoTo命令とラベルで疑似的にContinueと同じことができる。
ちなみに疑似コンティニューというのは私が勝手にそう呼んでいるだけだ。
Sub 疑似Continueを使ったForループ() For i = 1 To 10 If Not a = b Then GoTo Continue If Not c = d Then GoTo Continue If Not e = f Then GoTo Continue If Not g = h Then GoTo Continue MsgBox "OK" Continue: Next End Sub
これで一つでも条件に当てはまらなければあとの処理を飛ばしてContinueにジャンプする。考え方は早期リターンと同じで、条件に当てはまらない場合はさっさとジャンプするロジックである。
ネストは減るが、VBEによってラベルは一番左端に揃えられてしまうので、若干見た目はダサい。
GoTo文を使うと任意のラベルへジャンプできるが、好き勝手に使うとあっという間に手が付けられなくなる。
いわゆる黒魔術の類なので、初心者はここで紹介した用途以外では使わないことをお勧めする。
プログラマーのなかにはいかなる場合においても絶対にGoToを使うべきではないと固く信じている方もいる。GoToを使用する際は背後に人がいないのを確認してからどうぞ。
GoToについては以下もどうぞ。
thom.hateblo.jp
フラグ変数
さて、最後にフラグ変数を利用したネストの削減を紹介する。
まずBoolean型のフラグ変数を作成し、比較演算を直接If文に書くのではなくflagに比較演算の結果を格納していくというパターンだ。
Sub フラグ変数を利用する() Dim flag As Boolean flag = a = b If flag Then flag = c = d If flag Then flag = e = f If flag Then flag = g = h If flag Then MsgBox "OK" End If End Sub
これも冒頭のコードとロジック自体は同じになる。
またループと組み合わせても特段不都合は生じない。
Sub フラグ変数とループ() Dim flag As Boolean For i = 1 To 10 flag = a = b If flag Then flag = c = d If flag Then flag = e = f If flag Then flag = g = h If flag Then MsgBox "OK" End If Next End Sub
まとめ
よく、素直なコードを書こうということが言われる。もちろん基本はそうなんだけれど、もともと複雑なものを素直に書いたら、素直に複雑なコードが出来上がる。やはり複雑なコードはそれ相応に工夫して、シンプルさを維持していくという努力も必要なんじゃないかと思う。
たとえば早期リターンを使うと条件式が反転するのでIf文単体を見ると直観的ではなくなるかもしれないが、全体の構造を考えるとネストが減った文シンプルで理解しやすくなる。要は全体のバランスを考えて適宜コーディングの手法を選択することが大事だと思う。