VBAに限らずすべての言語に言えることだが、プロシージャが複雑になればなるほどバグが混入しやすくなる。
そこで今回はプロシージャの複雑度を測る「循環的複雑度」という指標を紹介する。
何がどう循環的なのか知らないけど、名前が醸し出すややこしさとは反対に、とてもシンプルに計算できるので身構える必要はない。
まず押さえておきたいのは、この指標は10以下が理想的で、30を超えるとヤバいということ。
数え方
以下の参考サイトに次の表記がある。
関数の循環的複雑度 = 1 + 関数の中に以下のものが出てきた回数:
if、while、for、foreach、case、default、continue、goto、&&、||、catch、?: (三項演算子)、??
参考:D.O.R.Y. : Dory Offers a Room for You: 循環的複雑度 ( Cyclomatic Complexity ) とは何ぞや
つまりこれをVBAの用語に翻訳すると、
プロシージャの循環的複雑度 = 1 + プロシージャの中に以下のものが出てきた回数:
If、ElseIf、Do、For、For Each、Case、GoTo、And、Or、Wend、IIf、Switch、Choose
ということになる。
※WhileではなくWendとしたのは、Do文のWhileキーワードと、While~Wend文のWhileキーワードが別物だから。
※Caseは、Select Case文の中身のCaseだけ数える。Selectキーワードの直後に来るCaseはノーカウント。
抜け漏れがあるかもしれないけど、主だったものは概ねカバーできてるはず。
勘違いしやすいポイントだが、Elseはカウントしない。
なぜならElseが明示的に書かれようと書かれまいと、分岐経路の数は変わらないから。
※ElseIfは別途経路が発生するのでカウントする。
ForやDoなどの繰り返しも、終了条件を満たしたらスキップされるという点で、分岐処理の一種といえる。
イメージしにくければ、最初から終了条件を満たしていればループの内部は一切通らないという点を考えてみると良い。
AndやOrは条件を集約しているが、以下のように分解して考えると経路の分岐にカウントされる理由が分かると思う。
↓Andの場合
'元のコード If A And B Then Result1 Else Result2 End If '条件を分解したコード If A Then If B Then Result1 Else Result2 End If End If
↓Orの場合
'元のコード If A Or B Then Result1 Else Result2 End If '条件を分解したコード If A Then Result1 ElseIf B Then Result1 Else Result2 End If
具体例
今私が作ってるマクロのプロシージャを例に循環的複雑度を測ってみる。
コードはこちら。
Sub ChangeProcessType(TargetShape As Shape, T As MsoAutoShapeType) Dim processConnectors As New Collection Dim s As Shape '現在TargetShapeに接続されたコネクタを一覧化しておく For Each s In TargetShape.Parent.Shapes If s.Connector Then If s.ConnectorFormat.BeginConnected Then If s.ConnectorFormat.BeginConnectedShape Is TargetShape Then processConnectors.Add _ Array(s, s.ConnectorFormat.BeginConnectionSite, True) 'True=Begin End If End If If s.ConnectorFormat.EndConnected Then If s.ConnectorFormat.EndConnectedShape Is TargetShape Then processConnectors.Add _ Array(s, s.ConnectorFormat.EndConnectionSite, False) 'False=End End If End If End If Next 'シェイプタイプを切り替える。 TargetShape.AutoShapeType = T 'このとき、コネクタの接続が外れる '一覧に登録されたコネクタをTargetShapeに再接続する Dim c As Variant For Each c In processConnectors Dim processConnector As Shape: Set processConnector = c(0) If c(2) Then 'True=Begin, False=End processConnector.ConnectorFormat.BeginConnect TargetShape, c(1) Else processConnector.ConnectorFormat.EndConnect TargetShape, c(1) End If Next End Sub
赤丸をつけて数えてみると、該当するものが8個。
初期値が1なので、1+8 = 9
循環的複雑度は9という結果になった。
10未満なのでこのプロシージャの複雑度は、まずまず良好と言える。
参考
複雑度の指標
szk-takanori.hatenablog.com
複雑度の計算方法
saturday-dory-fever.blogspot.com
ちなみにRubberduckというVBEのアドインを使うと、循環的複雑度(Cycromatic Complexity)を自動的に計測してくれる。
Rubberduckは導入するとVBエディタの初回起動がもたつくのと、そもそもPCスペックがそれなりにないと重たいのと、すべて英語なのと、設計思想が本業プログラマー寄りなので、万人にオススメできるものではない。
でもとても便利なツールなので興味があれば導入してみると良いかもしれない。