最近 Cyclomatic Complexity に遭遇したので、それをよりよく理解したいと思います。
複雑さの計算に入るさまざまな要因の実際のコーディング例は何ですか?具体的には、ウィキペディアの方程式M = E − N + 2P
、次の各用語の意味を詳しく理解したいと思います。
[〜#〜] e [〜#〜]または[〜#〜] n [ 〜#〜]は、コードブロック内の 決定点 (if、else if、for、foreachなど)の数である可能性がありますが、私はそうではありませんどちらがどちらを意味しているのかはかなりわかります。 [〜#〜] p [〜#〜]は関数呼び出しとクラスのインスタンス化を指していると思いますが、明確ではありません私が見ることができることを考えると、定義。誰かがそれぞれの明確なコード例でもう少し光を当てることができれば、それは役に立ちます。
フォローアップとして、循環的複雑度は100%に必要な単体テストの数に直接相関していますか パスカバレッジ ?例として、複雑度が4のメソッドは、そのメソッドをカバーするために4つの単体テストが必要であることを示していますか?
最後に、正規表現は循環的複雑度に影響を及ぼしますか?
式について:ノードは状態を表し、エッジは状態の変化を表します。すべてのプログラムで、ステートメントはプログラムの状態に変化をもたらします。連続する各ステートメントはEdgeで表され、ステートメントの実行後(または前)のプログラムの状態がノードです。
分岐ステートメントがある場合(if
など)-状態が2つの方法で変化する可能性があるため、2つのノードが出てきます。
循環的複雑度(CCN)を計算するもう1つの方法は、実行グラフ内の「領域」の数を計算することです(「独立領域」とは、他の円を含まない円です)。この場合、CCNは独立した領域の数に1を加えたものになります(これは、前の式で得られる数とまったく同じです)。
CCNはbranchingカバレッジ、またはpathカバレッジに使用され、同じです。 CCNは、単一のスレッド化されたアプリケーションで理論的に可能な異なる分岐パスの数と同じです( "if x < 2 and x > 5 then
"、しかしそれは到達不可能なコードとして良いコンパイラによって捕らえられるべきです。あなたは少なくともその数の異なるテストケースを持っている必要がありますそれぞれのケースが単一のパスをカバーすることを想定しているわけではありません。可能なテストケースでパスをカバーできない場合-到達できないコードを見つけました(実際には自分で証明する必要があります理由到達不能です。おそらくネストされているx < 2 and x > 5
どこかに潜んでいる)。
正規表現については-もちろん、他のコードと同様に影響します。ただし、正規表現コンストラクトのCCNは多すぎて単一の単体テストではカバーできず、正規表現エンジンがテスト済みであると想定して、テストのニーズに対する式の分岐の可能性を無視できます(ただし、もちろん正規表現エンジン)。
これについて私がぼんやりと書いているいくつかのコメント...
具体的には、M = E − N + 2PのWikipedia方程式の場合
その方程式は非常に間違っています。
何らかの理由で、McCabeは実際にそれを彼の 元の論文 で使用しています(「複雑さの測定」、IEEE Transactions on Software Engineering、Vo .. SE-2、No.4、12月1976) 、しかしそれを正当化せずに実際に最初のページで正しい式を引用した後、それは
v(G)= e-v + p
(ここでは、数式要素のラベルが変更されています)
具体的には、McCabeは本C.Berge、Graphs and Hypergraphs(以下G&HGと略記)を参照しています。 その本から直接 :
定義(G&HGの下部27ページ):
(無向)グラフG(複数の切断されたコンポーネントがある場合があります)のサイクロマティック数v(G)は、次のように定義されます。
v(G)= e-v + p
ここで、e =エッジの数、v =頂点の数、p =接続されたコンポーネントの数
定理(G&HGの29ページ上部)(McCabeでは使用されません):
グラフGの循環数v(G)は、独立したサイクルの最大数に等しい
cycleは、同じ頂点で開始および終了する一連の頂点であり、シーケンス内の連続する2つの頂点はそれぞれ、グラフ内で互いに隣接しています。
直感的に、歩行を重ね合わせることによって他のサイクルから構築できないサイクルがある場合、サイクルのセットはindependentです。
定理(G&HGの中央29ページ)(McCabeで使用):
強連結グラフGでは、循環数は線形独立回路の最大数と等しくなります。
circuitは、頂点とエッジの繰り返しが許可されていないサイクルです。
指定された方向のエッジを通過することによりすべての頂点が他のすべての頂点から到達可能である場合、有向グラフはstrongly connectedと呼ばれます。
ここでundirected graphsからstrongly connected graphsに渡したことに注意してください(これらは指示されています... Bergeはこれを完全に明確にしていません)
McCabeは、上記の定理を適用して、「McCabe Cyclomatic Complexity Number」(CCN)を計算する簡単な方法を導き出します。
手順の「ジャンプトポロジ」を表す有向グラフ(命令フローグラフ)が与えられ、指定された頂点は一意のentry pointを表し、指定された頂点は一意のexitを表すpoint(出口点の頂点は、複数の戻りがある場合は追加することによって「構築」する必要がある場合があります)、出口点の頂点から入口点の頂点に有向エッジを追加することにより、強連結グラフを作成します。エントリポイントの頂点を他のどの頂点からも到達可能にする。
McCabeは、修正された命令フローグラフのサイクロマティック数が「最小パス数」という直観的な概念に準拠していると(むしろ混乱を招くかもしれません)と仮定し、その数を複雑さの尺度として使用します。
クールなので、
変更された命令フローグラフの循環的複雑度の数は、無向グラフ内の「最小」の回路を数えることによって決定できます。これは人や機械で行うのは特に難しいことではありませんが、上記の定理を適用すると、さらに簡単にそれを決定できます。
v(G)= e-v + p
エッジの方向性を無視する場合。
すべての場合において、単一の手順を検討するだけなので、グラフ全体で接続されているコンポーネントは1つだけなので、次のようになります。
v(G)= e-v + 1。
追加された「exit-to-entry」エッジなしの元のグラフを検討する場合、単純に取得します。
ṽ(G)=ẽ-v + 2
ẽ= e-1として
彼の論文のMcCabeの例を使用して説明しましょう。
ここにあります:
サイクロマティック数の式は次のとおりです。
v(G)= e-v + p
5 = 10-6 + 1となるので、正しいです。
彼の論文で与えられた「McCabe循環的複雑度数」は
5 = 9-6 + 2(それ以上の説明はありません)
これはたまたま正しいです(それはv(G))を生成しますが、間違った理由のため、つまり次のように使用します。
ṽ(G)=ẽ-v + 2
したがって、ṽ(G)= v(G) ...やった!
2つの言葉で:それほどではない
for
ループとwhile
ループが同じ方法で処理されるという事実です(Cでは、for
を乱用してwhile
別の方法で;ここで私は厳密なfor (int i=0;i<const_val;i++)
ループについて話しています)。理論的なコンピューターサイエンスから、これらの2つの構成は完全に異なる計算能力を生み出すことがわかります: primitive-recursive functionsfor
のみを備えている場合、 部分的なμ再帰関数while
を備えている場合。フォローアップとして、循環的複雑度は、100%のパスカバレッジに必要な単体テストの数に直接相関していますか?
はい、基本的に。リファクタリングのタイミングを示す指標として、循環的複雑度を利用することもお勧めします。私の経験では、CCが低いほどテスト可能性と再利用可能性が大幅に向上します(実用的でなければなりませんが、過度のリファクタリングは行わないでください。また、一部のメソッドはその性質上、CCが高くなります。試行して強制することは必ずしも意味がありません。下)。
最後に、正規表現は循環的複雑度に影響を及ぼしますか?
はい、正確にしたい場合は、ほとんどのコード分析ツールがそのように考慮していないようですが。正規表現は有限の状態マシンにすぎないので、CCはFSMグラフから計算できると思いますが、かなり大きな数になるでしょう。