循環的な複雑さを理解する


11

最近、Cyclomatic Complexityに出くわしました。それをよりよく理解したいと思います。

複雑さの計算に使用されるさまざまな要因の実用的なコーディング例は何ですか?具体的には、Wikipediaの方程式についてM = E − N + 2P、次の各用語の意味をよりよく理解したいと思います。

  • E =グラフのエッジの数
  • N =グラフのノードの数
  • P =接続されたコンポーネントの数

EまたはNのいずれかが、コードブロック内の決定ポイント(if、else、for、foreachなど)の数であると思われますが、どちらがどちらを意味するのかはよくわかりません。また、Pは関数呼び出しとクラスのインスタンス化を指すと推測していますが、私が見ることができる明確な定義はありません。誰かがそれぞれの明確なコード例でもう少し光を当てることができれば、それは助けになるでしょう。

フォローアップとして、Cyclomatic Complexityは、100%パスカバレッジに必要な単体テストの数と直接相関していますか?例として、複雑度4のメソッドは、そのメソッドをカバーするために4つのユニットテストが必要であることを示していますか?

最後に、正規表現は循環的複雑度に影響しますか?


WikipediaからMcCabeがオリジナルの論文を入手できることを発見しました。GoogleBooksはMcCabeが彼のオリジナルの論文に使用した本を生成します。興味深いことに、その後、McCabeは元の定理を誤って使用していることがわかります(また、無向グラフから開始する必要があるため、最初から強く結び付ける必要がないため、混乱を招きます)正しい式はM = E + 1-N + Pになりますが、Pは常に1であるため...)現代の「例外処理」はスパナをそのメトリックの動作に投げ込むと考えられます。
デビッドトンホーファー

...そして再帰呼び出しについてはどうでしょうか(関数チェーンを経由する可能性があります)。関数グラフを融合しますか?「&&」のようなブール演算子の短絡についてはどうですか。refがnullの場合にnullを生成する「ref?.x」などの保護された演算子 まあ、それは単なる別の指標です。しかし、ここには小さな大学プロジェクトのためのいくつかの仕事があります。
デビッドトンホーファー

回答:


8

式に関して:ノードは状態を表し、エッジは状態の変化を表します。すべてのプログラムで、ステートメントはプログラムの状態に変化をもたらします。連続する各ステートメントはエッジで表され、ステートメントの実行後(または実行前)のプログラムの状態はノードです。

分岐ステートメントがある場合(ifたとえば)-状態は2つの方法で変更できるため、2つのノードが出ています。

Cyclomatic Complexity Number(CCN)を計算するもう1つの方法は、実行グラフ内の「領域」の数を計算することです(「独立領域」は他の円を含まない円です)。この場合、CCNは独立領域の数に1を加えたものになります(これは、前の式で与えられるものとまったく同じ数になります)。

CCNは、同じカバレッジブランチまたはパスカバレッジに使用されます。CCNは、シングルスレッドアプリケーションで理論的に可能なさまざまな分岐パスの数に等しくなります(「if x < 2 and x > 5 then」などの分岐を含む場合がありますが、適切なコンパイラによって到達不能コードとして捕捉される必要があります)。少なくともその数の異なるテストケースが必要です(一部のテストケースは以前のテストケースでカバーされたパスを繰り返している可能性がありますが、各ケースが単一のパスをカバーしていることを前提としています)。可能性のあるテストケースでパスをカバーできない場合-到達不能なコードを発見しました(到達不可能な理由を実際に証明する必要がありますが、おそらくx < 2 and x > 5どこかに潜んでいる入れ子です)。

正規表現に関しては-もちろん、他のコードと同様に影響します。ただし、正規表現コンストラクトのCCNはおそらく単一のユニットテストでカバーするには高すぎるため、正規表現エンジンがテスト済みであると想定し、テストニーズに対する式の分岐の可能性を無視できます(ただし、もちろん正規表現エンジン)。


2
+1:実際には、正規表現エンジンがテストされたことを信頼する必要があります。信頼できない場合は、信頼できるものを入手してください
-S.ロット

「CCNは、単一のスレッド化されたアプリケーションで可能な異なる実行パスの数に等しい」 CCNはその意味ではなくコードのトポロジのみに基づいているため、これは誤りです。これらのパスのかなりの割合は、設定できない入力状態を要求するため、行使できない可能性があります(一部のxは5でたとえば2未満です)。率直に言って、実行するテストケースを決定するためにCCNを使用するのは間違っていると思います。CCNは、開発者に「ここに行き過ぎたかもしれません。リファクタリングを検討してください」と伝える番号です。そして、それでも、高いCCNの正当な理由があるかもしれません。
デビッドトンホーファー

1
@Davidは、それに対処する文を追加しました。CCNはブランチカバレッジであり、低レベルでCCNが高くなる正当な理由はありません(通常、個々の機能ごとに強制することをお勧めします)。
littleadv

ブランチカバレッジとパスカバレッジは同じではありません。ブランチカバレッジはすべてのブランチをカバーすることを目的としていますが、パスカバレッジはブランチのすべての組み合わせをカバーすることを目的としています。
ムーヴィシエル

13

これについてのいくつかのコメントは、私がぼんやりと書いている...

具体的には、M = E − N + 2Pのウィキペディア方程式について

その方程式は非常に間違っています。

何らかの理由で、マッケイブは確かに彼の中にそれを使用して、元の論文(「A複雑さの測度」、ソフトウェア工学、Voの上のIEEEトランザクション.. SE-2、第4号、1976年12月)、それを正当化することなく、かつ、実際に正しいを引用した後、最初のページの

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)は、独立サイクルの最大数に等しい

サイクルは、グラフ内の互いに隣接する配列内の各二つの連続する頂点で、同じ頂点で開始および終了頂点の配列です。

直観的には、歩行の重ね合わせによって他のサイクルからサイクルを構築できない場合、サイクルのセット独立しています。

定理(G&HGの29ページ中)(McCabeで使用):

強く接続されたグラフGでは、循環数は線形独立回路の最大数に等しくなります。

回路は許容頂点と辺のない繰り返しを有するサイクルです。

有向グラフは、指定された方向にエッジを通過することにより、すべての頂点が他のすべての頂点から到達可能である場合、強く接続されていると言われます。

ここでは、無向グラフから強く接続されたグラフに渡されていることに注意してください(これは有向グラフです... Bergeはこれを完全に明らかにしていません)。

McCabeは上記の定理を適用して、「McCabe Cyclomatic Complexity Number」(CCN)を計算する簡単な方法を導き出しました。

プロシージャの「ジャンプトポロジ」を表す有向グラフ(命令フローグラフ)を指定し、指定された頂点が一意のエントリポイントを表し、指定された頂点が一意の出口ポイントを表す(出口ポイントの頂点を「構築」する必要がある場合がある)複数の戻り値の場合に追加することで)、出口点頂点からエントリポイント頂点に有向エッジを追加することにより、強力に接続されたグラフを作成し、エントリポイント頂点が他の頂点から到達できるようにします。

McCabeは、修正された命令フローグラフの循環的数が「「最小パス数」の直感的な概念に準拠している」と(むしろ紛らわしく言うかもしれません)ので、その数を複雑さの尺度として使用します。

クールだから:

変更された命令フローグラフの循環的複雑度数は、無向グラフの「最小」回路をカウントすることで決定できます。これは人や機械で行うのは特に難しいことではありませんが、上記の定理を適用することで、より簡単に決定できます。

v(G)= e-v + p

エッジの方向性を無視する場合。

すべての場合において、単一の手順を検討するだけなので、グラフ全体に接続されているコンポーネントは1つだけです。

v(G)= e-v + 1。

「出口への出口」エッジが追加されていない元のグラフ検討する場合、単純に以下を取得します。

ṽ(G)=ẽ-v + 2

as = e-1

彼の論文のMcCabeの例を使用して説明しましょう。

マッケイブの例

ここにあります:

  • e = 10
  • v = 6
  • p = 1(1つのコンポーネント)
  • v(G)= 5(明らかに5サイクルをカウントしています)

サイクロマティック数の式は次のとおりです。

v(G)= e-v + p

5 = 10-6 + 1となるので正しい!

彼の論文で与えられている「McCabe循環的複雑度数」は

5 = 9-6 + 2(方法についての詳細な説明はこの論文にはありません)

これはたまたま正しい(v(G)になります)が、間違った理由で、つまり以下を使用します:

ṽ(G)=ẽ-v + 2

したがって、ṽ(G)= v(G)... phew!

しかし、これは良いことでしょうか?

2つの言葉で:あまりない

  • プロシージャの「命令フローグラフ」を確立する方法は、特に例外処理と再帰が図に入った場合、完全に明確ではありません。McCabeは、再帰、例外、および単純な実行構造を持たない言語であるFORTRAN 66で記述されたコードに彼の考えを適用したことに注意してください。
  • 決定を伴う手順とループを伴う手順が同じCCNを生成するという事実は、良い兆候ではありません。

ここに画像の説明を入力してください


1
@JayElston良いキャッチ。確かにそうです。修繕!
デビッドトンホーファー

1
元の論文にリンクするための大きな+1。その頃に書かれた論文の多くは、中級レベルのプログラマーにとって非常に読みやすく、読むべきです。
ダニエルT.

1

フォローアップとして、Cyclomatic Complexityは、100%パスカバレッジに必要な単体テストの数に直接相関していますか?

はい、基本的に。また、リファクタリングのタイミングを示す指標として循環的複雑度を利用することをお勧めします。私の経験では、CCが低いほどテスト性と再利用性が大幅に向上します(ただし、実用的である必要があります-過剰なリファクタリングをしないでください。また、一部のメソッドはその性質上CCが高くなります-強制的に実行することは必ずしも意味がありません)下)。

最後に、正規表現は循環的複雑度に影響しますか?

はい、正確にしたい場合は、ほとんどのコード分析ツールはそのように考慮に入れていないようです。正規表現は単なる有限状態マシンであるため、それらのCCはFSMグラフから計算できると推測していますが、かなりの数になります。


+1-RegExesのCCを計算するのは楽しい仕事ではないと思います。
VirtuosiMedia
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.