対応コードメトリクス

この章は、big-code-analysis が計算するすべてのメトリクスのガイドツアーです。各セクションは元の研究論文から出発してアルゴリズムを順に解説し、そのメトリクスが 本来 意図されていた使われ方と、数年後に業界が実際に行き着いた使われ方の両方を説明します。ソフトウェアメトリクスに不慣れな場合は、セクションを順番に読んでください — 後半のメトリクス(特に保守容易性指数)は、前半のメトリクス(Halstead、循環的複雑度、LOC)の上に明示的に構築されています。

始める前に、いくつかの前提となる注意点を挙げます:

  • メトリクスは測定値であって、判定ではありません。 このページのすべての数値は、ソースコードの構造的な性質を要約したものです。正しさ、生産性、開発者のスキルを測るものは一つもありません。どのメトリクスにとっても最も重要な問いは常に「何と比較して?」です — 1 か月前の同じモジュール、兄弟モジュールと比べたこのモジュール、業界のベースラインと比べたこのコードベース、というように。絶対的なしきい値は、せいぜい大まかなヒューリスティックにすぎません。
  • ここにあるほとんどのメトリクスは 3 つのスコープで計算されます関数 / メソッド ごと、 クラスやユニット的なスペース ごと、そして 「ファイル」 ごとです。基盤となる tree-sitter パーサーは「スペース」(関数、クロージャ、クラス、名前空間など)のツリーを生成し、すべてのメトリクスはそのツリーを通じてロールアップされます。
  • オブジェクト指向メトリクスは、オブジェクト指向の構文に対してのみ反応します。 WMC、NPA、NPM は、impl ブロックのない Rust ファイルやクラスのない Python モジュールでは 0 を報告します。これは正しい答えであり、バグではありません。

一覧

メトリクス測定対象初出
ABC<Assignments, Branches, Conditions> として表したサイズFitzpatrick, 1997
認知的複雑度関数を 読む のがどれだけ難しいかCampbell / SonarSource, 2017
循環的複雑度(CC)関数内の独立した経路の数McCabe, 1976
Halstead語彙に基づくサイズ、難易度、労力、バグ数Halstead, 1977
コード行数(SLOC、PLOC、LLOC、CLOC、BLANK)生・物理・論理・コメント・空白の各行数Conte, Dunsmore & Shen, 1986
保守容易性指数(MI)複合的な保守性スコアOman & Hagemeister, 1992; Coleman et al., 1994
NArgs関数ごとの引数の数慣習的なメトリクス
NExits関数ごとの出口点の数構造化プログラミングの文献
NOMメソッドとクロージャの数Lorenz & Kidd, 1994
NPA公開属性の数Lorenz & Kidd, 1994
NPM公開メソッドの数Lorenz & Kidd, 1994
TokensTree-sitter のリーフトークン数(サイズの代理指標)Lizard ツール、Terry Yin
WMCクラスの全メソッドにわたる循環的複雑度の合計Chidamber & Kemerer, 1994

ABC

ABC メトリクスは、コード片のサイズを 3 次元ベクトルとして測定します。各成分は 1 種類の操作をカウントします:

  • Assignments(代入)— 値を変数に格納するあらゆる操作で、複合代入(+=++)や明示的な初期化を含みます。
  • Branches(分岐)— 関数およびメソッドの 呼び出し です。名前に反して、これは条件ジャンプの数ではありません。制御が他のコードへと分岐していく地点の数です。
  • Conditions(条件)— ブール判定です。比較演算子(==!=<=>=<>)、三項演算子(?)、および固定のキーワード集合(elsecasetrycatch)が対象です。default / ワイルドカードアームはどの言語でもカウントされません(後述の言語別の逸脱を参照)。短絡論理演算子 &&|| は単独ではカウントされず、代わりに && / || チェーンの比較でない各オペランドが、Fitzpatrick の「単項条件式(unary conditional expression)」規則によって 1 条件として寄与します。次の節では、規則、言語別の逸脱、および実例を順に説明します。

このメトリクスは、Jerry Fitzpatrick が 1997 年の C++ Report 誌の記事 Applying the ABC metric to C, C++ and Java で導入しました。現代の言語で何を ABC としてカウントするかの規則を含む現行の正準仕様は、Fitzpatrick の Software Renovation サイトで維持されています。

カウント規則

Fitzpatrick の論文は規則を 3 つの図で列挙しています — Figure 2(C)、Figure 3(C++、Figure 2 を拡張)、Figure 4(Java)です。Big-code-analysis はこれらの規則集合を言語ごとにそのまま実装しています。以下の表は、各コンポーネントで何がカウントされるかを、各行をそれを導入した図に帰属させてまとめたものです。

代入

規則A としてのカウント初出
単純代入(=出現ごとに 1Figure 2(C)
複合代入(+=-=*=/=%=<<=>>=&=\|=^=出現ごとに 1Figure 2(C)/ Figure 4(Java)
Java の符号なし右シフト代入(>>>=出現ごとに 1Figure 4(Java)
前置または後置のインクリメント / デクリメント(++--出現ごとに 1Figure 2(C)
初期化を伴うコンストラクタ呼び出し出現ごとに 1Figure 3(C++)

分岐

規則B としてのカウント初出
関数またはメソッドの呼び出し呼び出し箇所ごとに 1Figure 2(C)/ Figure 4(Java)
new 演算子出現ごとに 1Figure 3(C++)/ Figure 4(Java)
delete 演算子出現ごとに 1Figure 3(C++)
goto labelbreak labelcontinue label出現ごとに 1Figure 2(C)/ Figure 3(C++)/ Figure 4(Java、ラベル付き break / continue のみ — Java に goto はありません)

条件

規則C としてのカウント初出
比較演算子(==!=<=>=<>出現ごとに 1Figure 2、Rule 5
三項演算子 ? :出現ごとに 1Figure 2、Rule 5
elsecase出現ごとに 1Figure 2、Rule 5
プリプロセッサの #else#elif出現ごとに 1Figure 2、Rule 5
trycatch出現ごとに 1Figure 3(C++)/ Figure 4(Java)
単項条件式&& / \|\| の比較でないオペランドごとに 1(さらに if / while / 引数 / return スロット内の、! で包まれた条件または裸の真偽値条件ごとに 1)Figure 3、Rule 7 / Figure 4、Rule 9

短絡論理演算子(&&||、および各言語の同等物 — Ruby の and / or、Python の and / or、Perl の and / or / xor、Lua の and / or、Tcl の && / ||、iRules の && / || / and / or)は、単独では条件に寄与しません。代わりに、単項条件規則によって比較でない各オペランドが 1 条件として寄与します。論文はこれを 2 か所で明示しています。

  1. Listing 2(am >= 0 && am <= 0xF) ? '/' : 'C'accc と注記しています — 代入 1 つと条件 3 つで、3 つの条件は 2 つの比較(>=<=)と三項演算子(?)です。&& 自体の寄与はゼロです。
  2. 一方 Rule 7 / Rule 9 は各オペランドをカウントします。if (x || y) printf("test failure\n"); について、論文は「xy の両方が条件式として評価されるため、単項条件は 2 つある」と記しています。ここでも || の寄与はゼロで、xy がそれぞれ 1 ずつ寄与します。

言語別の逸脱

言語ごとの impl Abc ブロックは、その言語に相当する構文がない場合や、論文の規則を字義どおり適用すると過剰カウントになる場合に、規則集合を絞り込んでいます。

言語逸脱理由
C、Go、Rusttry / catch を省略文法に try/catch キーワードがありません。エラー処理には errno / Result / Result 系の直和型を使います。
Rubycatch の代わりに RescueRuby の例外処理キーワードは rescue であり、AST ノード Rescue が Java の catch の役割を果たします。
全言語default / _ ワイルドカードアームを条件集合から除外Fitzpatrick の Figure 2 は default を挙げていますが、これは無条件にフォールスルーします — カウントすると、本体に関係なくすべての switch / matchC が膨らみます。big-code-analysis はすべての言語でこれを省略します(Rust の _ => アームや Java の default: アームも含みます)。
Tclチェーンオペランドの単項条件は実装済み。裸の真偽値 / 引数 / return スロットは未実装expr {…} 内の && / \|\| チェーンの各オペランドが 1 条件としてカウントされるため、if {$a && $b} は 2 を報告します。より広い Phase 2B のスロット振り分けは未実装のため、裸の真偽値の if {$a} は依然として 0 を報告します。
iRulesチェーンオペランドの単項条件は実装済み(同系の Tcl と異なります)。裸の真偽値 / 引数 / return スロットは未実装&& / \|\| / and / or チェーンの各オペランドが 1 条件としてカウントされる(Rule 9)ため、if {!$a && !$b} は 2 を報告します。iRules は Tcl にはない語形式の文字列一致比較子(containsstarts_withends_withequalsmatches など)も認識します(Tcl の eq / ne / in / ni は共通です)。より広い Phase 2B のスロット振り分けは未実装のため、裸の真偽値の if {$a} は依然として 0 を報告します。
All Phase 2 languages (Java, Groovy, C#, Rust, Go, JavaScript, TypeScript, TSX, Mozjs, PHP, C, C++, Objective-C, Mozcpp, Python, Perl, Lua)if (true) {}m(!a, !b)return !x はそのオペランドをカウントPhase 2B routes if / while / do-while / argument-list / return slots through the same walker, so the rule applies uniformly across decision-bearing positions. A bare return x continues to report zero — Fitzpatrick treats an identifier in a return slot as a value, not a unary conditional.
Ternary slots: Java, Groovy, C#, C, C++, Objective-C, Mozcpp, JavaScript, TypeScript, TSX, Mozjs, PHP, Perla ? !b : !c counts its condition and both branch operandsThe same walker also runs over a ternary's three operand slots, so a ? !b : !c reports 4 (the ? plus three unary conditions) rather than 1. Languages with no ternary (Rust, Go, Kotlin, Lua, Elixir) are unaffected. Ruby, Python, Tcl and iRules do have one but do not yet route it (issue #1161).
Ruby裸の述語の if / unless / while / until(ブロック形式と修飾子形式)は 1 条件としてカウント慣用的な Ruby は裸の述語(if flagx if flag)を好みます。条件スロットをカウントすることで、ABC の条件数が Ruby の循環的複雑度の判断数以上に保たれます(他の言語でも同じ整合が強制されています)。述語内の比較(if a == b)や && / \|\| チェーンは、それ自身の演算子 / ウォーカーのアームでカウントされ、二重カウントはされません。
Bashif / elif / while と、ワイルドカードでない各 case アームは 1 条件としてカウントBash の述語は コマンド であるため、埋め込まれたブール式ではなく、分岐キーワード自体が条件のシグナルです。それぞれが Bash の循環的複雑度の判断に対応します。裸の *) case アーム(default: に相当)は除外され、循環的複雑度の標準カウントに合わせています。
Kotlincatch に加えて try も 1 条件としてカウントFitzpatrick は両方のキーワードをカウントし、Java / C# / C++ / Groovy はすでに両方をカウントしています。Kotlin は以前は catch ブロックのみをカウントしていました。

実例

次の C 関数を考えます。

char digit_or_C(int am) {
    char c;
    if (am >= 0 && am <= 0xF) {
        c = '/';
    } else {
        c = 'C';
    }
    return c;
}

関数本体を走査すると、次のようになります。

トークン / 構文コンポーネント理由
am >= 0C += 1比較(Rule 5、>=
am <= 0xFC += 1比較(Rule 5、<=
&&論理演算子 — 単独では寄与しません。
if/elseC += 1else キーワード(Rule 5)
c = '/'A += 1代入(Rule 1)
c = 'C'A += 1代入(Rule 1)

合計: <A,B,C> = <2, 0, 3>、マグニチュードは √13 ≈ 3.61 です。

同じ本体を単項条件を使って書き換えると —

if (am_in_range || force_letter) {
    c = 'C';
}

ウォーカーは am_in_rangeforce_letter をそれぞれ 1 回ずつカウントします(Rule 7 / 9 の単項条件)。|| 演算子自体の寄与はゼロです。これは論文にある Fitzpatrick の Rule 7 / 9 の実例 if (x || y) printf("test failure\n"); — 「x と y の両方が条件式として評価されるため、単項条件は 2 つある」— と一致します。

他の ABC ツールとの比較

本プロジェクトは && / || について Fitzpatrick の原論文に従います。演算子自体はカウントせず、比較でない各オペランドを単項条件として 1 回ずつカウントします。これは(and / or を直接カウントする)RuboCop の Metrics/AbcSize とは異なり、StepicOrg/abcmeter および eoinnoble/python-abc と一致します。ツール間で ABC の数値を比較するとき、同じソースに対する食い違いの最大の原因は、この演算子カウントの選択です。

アルゴリズム

実装は構文木のすべての葉ノードをちょうど 1 回ずつ走査します。各ノードについて、その言語の言語別 Abc トレイト実装に 3 つの yes/no の質問 — これは代入か?分岐か?条件か? — を尋ね、一致したカウンタをインクリメントします。4 つの主要値は次のとおりです。

  • 3 つのコンポーネントそのもの(assignmentsbranchesconditions)。
  • マグニチュード |<A,B,C>| = √(A² + B² + C²)。Fitzpatrick がベクトルを単一の数値へ要約する方法として推奨しているものです。

完全なシリアライズ出力(src/metrics/abc.rs)は、これら 4 つに加えて、value(CLI がしきい値判定に使うスペースごとのマグニチュードで、葉スペースでは magnitude と等しい値)、コンポーネントごとの平均(assignments_averagebranches_averageconditions_average)、およびファイルスコープでのコンポーネントごとの *_min / *_max を出力し、合計 14 フィールドになります。このメトリクスは src/languages/language_*.rs で言語ごとに特殊化されています。

読み方

ABC は複雑度メトリクスではなく サイズ メトリクスです — 判断を持たない長く単調な関数でも、代入を多く行えば高いスコアになります。Fitzpatrick の当初の推奨は、マグニチュードを相対的な物差しとして使うことでした。すなわち、ファイル内の関数を ABC マグニチュードで順位付けし、上位 1 割に注目します。

実際には、ABC は Ruby コミュニティで最も広く採用されるようになりました。rubocop リンタflog ツール は、どちらもデフォルトでしきい値ベースの警告を出します。ABC マグニチュードがおよそ 17 を超える Ruby メソッドは慣例的にリファクタリング候補とされ、30 を超えると保守が難しいと見なされます。これらのしきい値は言語固有です — 明示的なゲッター / セッター代入をより多用する C++ や Java では、より高い値になると考えてください。

認知的複雑度

認知的複雑度は、SonarSource の G. Ann Campbell が 2017 年のホワイトペーパー Cognitive Complexity — A new way of measuring understandability と、その後の IEEE TechDebt 2018 論文 Cognitive Complexity — An Overview and Evaluation で導入しました。ホワイトペーパー自体は SonarSource のサイトで CognitiveComplexity.pdf として入手できます。

このメトリクスは、コード品質ツールにおける循環的複雑度の意図的な置き換えとして設計されました。Campbell の主張は、循環的複雑度が測るのはコードの テスト のしにくさであって、理解 のしにくさではない、というものです。1024 アームの switch 文は、同一のロジックを実行する深くネストした if の連鎖と同じスコアになりますが、人間の読み手にとってはネストしたコードの方がはるかに追いにくいのです。

アルゴリズム

認知的複雑度はゼロから始まり、木を走査しながら次の 3 つの規則を適用します。

  1. 「省略形」の制御フローは不問にします。 単一のブロックへ誘導するだけの構文 — ネストのないトップレベルの if、自前の条件を持たない elsefor のヘッダ、?: 三項演算子 — は、それぞれ基本の +1 を加えますが、そのパターン自体へのペナルティはありません。
  2. 線形フローの中断にはペナルティを課します。 すべての ifelse ifelseswitchtry/catch、ループ、ジャンプ(gotobreak labelcontinue label)、再帰呼び出しは、少なくとも +1 を加えます。
  3. ネストを罰します。 すでにネストされたブロックの 内側 に制御フローが現れるたびに、メトリクスは ネストの深さごとに 追加の +1 を加えます。メソッド内の外側の if の中の for の中の if1 + 2 + 3 = 6 になりますが、同じ 3 つの構文をフラットに並べた場合は 1 + 1 + 1 = 3 です。

同一のブール演算子の連なり(a && b && c)は、&& の連鎖は単一の && より読みにくいわけではないという理由で、連なり全体で +1 になります。演算子が切り替わる(a && b || c)ところで認知負荷が跳ね上がるため、2 つ目の演算子は独自の +1 を得ます。

big-code-analysis は、関数ごとの構造スコアに加えて、ファイル全体の summinmax と関数ごとの average を出力します。実装は src/metrics/cognitive.rs にあります。

読み方

認知的複雑度が 0 の関数は純粋に線形で、分岐もループもありません。SonarSource のツールはデフォルトで 15 を超える関数を「複雑すぎる」とフラグし、Campbell がホワイトペーパーで推奨しているのは、関数がおよそ 25 を超えることはめったにないようにすることです。循環的複雑度と異なり、このメトリクスは滑らかにスケールします。判断の数が同じでも、深くネストしたコードはフラットなコードより大幅に高いスコアになります。

後から定着したユースケースはコードレビュー時のリファクタリング指針です。このメトリクスはネストを特にペナルティ対象とするため、早期リターンや「メソッドの抽出」リファクタリングの恩恵を受けるような関数をまさに検出する傾向があります。SonarLint の IDE プラグイン(IntelliJ、VS Code、Visual Studio、Eclipse)はいずれも、ホバー時の代表的な複雑度としてこの値を表示し、このメトリクスはその後、Sonar エコシステム外のいくつかの言語サーバーやコードレビュープラットフォームにも採用されています。

言語別の逸脱

  • Elixir は再帰やジャンプ文をスコアしません。Elixir の制御フロー(if / unless / cond / case / with)は、専用の文法プロダクションではなくマクロ形の Call ノードから構築されており、言語に break / continue / goto はありません。そのため実装は、識別できるネストを伴う構文のみをスコアし、これらの形を構文として公開する言語向けに SonarSource 仕様が追加する再帰(B3)と非構造化ジャンプ(B2)の加算を省略します。

  • For every language with a syntactic function-definition node, a nested function — a local function, or a method on a local / inner class — resets the nesting counter to zero at its boundary and adds a function-depth surcharge, so control flow inside it is scored against the nested function's own depth rather than the enclosing function's nesting. Byte-equivalent constructs therefore score identically across languages.

  • A lambda or closure (x -> …, |x| …, lambda x: …, a Ruby block, an Objective-C block) is not a function boundary in that sense. It adds a surcharge on top of the enclosing nesting instead of replacing it, so a decision inside a lambda written inside an if is charged for both.

  • Python charges a boolean operator an extra +1 for each enclosing lambda, on top of the +1 the boolean sequence itself earns. No other language does this. Only the outermost operator inside a given lambda body pays the surcharge, and the walk that counts those lambdas stops at the nearest enclosing expression_list. (It also stops at if, for and while, but a lambda body is a single expression, so no lambda ever sits above one of those statements and those three arms never change a count.) The sequence increments themselves still follow the operator-switch rule above, so a mixed chain inside one lambda pays two of them plus a single surcharge. Measured:

    Python sourcecognitive.sum
    g = a and b1
    g = lambda y: y and a2
    g = lambda y: y and a and b2
    g = lambda y: y and a or b3
    g = lambda x: (lambda y: y and a)3
    k = lambda q: (yield a and b, c)1
    def f(a): g = lambda x: x0

    The last two rows are the boundaries. The parenthesised yield puts an expression_list between the operator and the lambda, ending the walk before it reaches one, so only the fundamental +1 is left; and a lambda containing no boolean operator costs nothing by itself.

    Campbell gives a boolean sequence a fundamental increment and no nesting increment, so this is an addition to the specification rather than an implementation of it. Issue #1150 reviewed the rule and kept it deliberately. Python's boolean-operator cost is not comparable with another language's score for the same code.

循環的複雑度(CC)

最初のソフトウェア複雑度メトリクスで、Thomas J. McCabe が 1976 年に A Complexity Measure(IEEE Transactions on Software Engineering, SE-2(4), 308–320 ページ)で導入しました。

McCabe のアイデアは、関数の 制御フローグラフ にグラフ理論を適用することでした。各基本ブロックをノード、ブロック間の各ジャンプをエッジとして描くと、そのグラフのサイクロマティック数は次のようになります。

M = E − N + 2P

ここで E はエッジ数、N はノード数、P は連結成分の数です。重要なのは、M が関数を通る線形独立パスの数、言い換えれば、すべての分岐を少なくとも 1 回カバーするのに必要な最小のテストケース数と正確に一致することです。

アルゴリズム

big-code-analysis は制御フローグラフを文字どおりに構築するわけではありません。代わりに、McCabe が 1976 年の論文で構造化プログラムについて証明した、等価ではるかに安価な定式化を使います。

循環的複雑度 = 1 + (判断点の数)

「判断点」とは、制御が分岐しうるあらゆるノードのことです。

  • ifelse if、三項演算子 ?:
  • switch / match / select 内の case / when アーム
  • whiledo … while、あらゆる種類の for
  • 例外ハンドラの catch
  • 短絡ブール演算子 &&||

src/metrics/cyclomatic.rs にある言語別の Cyclomatic トレイトが、各 tree-sitter ノードに「これは判断か」を問い合わせてカウンタをインクリメントします。このメトリクスは関数ごととファイルごとにロールアップされます。メソッド本体をまたぐクラス単位の集約は、後述の WMC が別途提供します。

修正循環的複雑度

big-code-analysis は、単一の switch 文の中のすべての case / match / when アームを、アーム数に関係なく 1 つの判断点に折りたたむ修正(modified) 版も報告します。これは大きなディスパッチテーブルを少なめにカウントする傾向があり、厳密な McCabe の定義よりも開発者の直感に合うことがよくあります — 30 アームの enum ディスパッチは、30 ではなく 1 つの判断として読まれるからです。(この慣例自体は本プロジェクト独自のものではありません。Terry Yin の lizard ツールに古くからある -m モードを踏襲したもので、多くの読者はそこで初めて目にしているはずです。)両方の数値は並べて出力されます。どちらか一方を選び、一貫して使ってください。

Rust の ? 演算子のカウント

デフォルトでは、Rust の ? 演算子(文法ノード try_expression)は、標準と修正の両方の循環的複雑度に +1 を加えます。これはアップストリームの rust-code-analysis と同じ挙動で、? は早期リターンの分岐だからです。循環的複雑度を保守性の 「ゲート」 として使う場合、この挙動は、ハッピーパスにいくつかの ? を通しているだけの、線形だが失敗しうるコードに過剰なペナルティを与えることがあります。次の方法でオプトアウトし、? を線形なエラー伝播として扱えます。

  • ライブラリ: MetricsOptions::default().with_count_cyclomatic_try(false)
  • CLI: --cyclomatic-count-try=false(または非推奨の --no-cyclomatic-try エイリアス)、あるいは bca.tomlcyclomatic_count_try = false(CLI の値はどちらの方向でもこのキーを上書きします)。
  • リポジトリのゲート: 自動検出される bca.tomlcyclomatic_count_try = false を設定します(本プロジェクト自身の make self-scan はまさにこれを行っており、フラグも環境変数も使いません)。このポリシーの切り替えは循環的複雑度の値を変化させるため、同じ変更の中で .bca-baseline.toml を再生成してください。

デフォルトは変わりません — ? は引き続きカウントされるため、公開済みのメトリクス値は保たれます。このトグルは Rust 専用です。他の言語は try_expression を出力しません。

読み方

McCabe の当初の推奨は、1976 年の論文で繰り返し述べられ、NIST の Structured Testing レポート(Special Publication 500-235、1996 年)にも引き継がれているもので、単一の関数の上限を 10 とすることです。これを超えると、分岐カバレッジに必要なテストケース数が不快なほど増大します。

循環的複雑度の定着した用途は次のとおりです。

  1. 欠陥予測。 複雑度は — 完全ではないものの — 関数がバグを含む 確率 とよく相関し、ほとんどの静的解析ツールは CC の高い関数をリスクありとフラグします。
  2. テストカバレッジ計画。 CC はすべての分岐を網羅するために必要なテストケース数の下限であるため、テストチームは工数の見積もりに直接利用します。
  3. リファクタリングのトリアージ。 循環的複雑度はほぼすべてのコード品質ダッシュボードで筆頭に挙げられる「複雑度」の数値であり、長さが同程度に見える 2 つの関数の優先順位を決めるタイブレーカーとしてもよく使われます。

このメトリクスのよく知られた盲点に注意してください。すべての判断を同じ重みとして扱う点です。enum に対する 30 分岐の switch と、それぞれがさらにネストした if を内包する 2 つのネストした if を持つ関数は、読む体験としてはまったく異なるにもかかわらず、どちらもおよそ 30 というスコアになります。認知的複雑度(前述)は、まさにこの点を解決するために設計されました。

Halstead

Halstead スイートは、このページで最も歴史の古いサイズと工数のメトリクスファミリーです。Maurice H. Halstead が 1977 年の著書 Elements of Software Science(Elsevier、ISBN 0-444-00205-7)で導入したもので、Wikipedia の Halstead complexity measures のページに公式がまとめられています。Halstead の構想は驚くほど野心的でした。物理学が物質を対象とする経験科学であるのと同じ意味で、定量的・経験的な ソフトウェアの科学 を打ち立てようとしたのです。

4 つの基本カウント

Halstead はプログラムをトークンに還元し、それらを 2 つのカテゴリに分割します。

  • 演算子(operator) — 何かを 行う もの。キーワード(ifreturnwhile)、算術・論理演算子、代入、関数呼び出しの構文、制御フローを担う句読記号です。
  • 被演算子(operand) — 何かで ある もの。識別子とリテラルです。

これらから 4 つの基本カウントを導出します。

JSON キー記号意味
unique_operatorsn1相異なる演算子の数
unique_operandsn2相異なる被演算子の数
total_operatorsN1演算子の出現の合計
total_operandsN2被演算子の出現の合計

シリアライズされた出力では説明的な JSON キー列の名前を使用します。後述の導出メトリクスの公式では、同じ 4 つのカウントに対して Halstead の古典的な n1/N1/n2/N2 表記を使用します。

big-code-analysis はこれら 4 つの数値を src/metrics/halstead.rs で関数ごと・ファイルごとに記録します。言語ごとのトレイトが、トークン単位で演算子か被演算子かを分類します。このルールは括弧や文の区切りのような純粋なレイアウト用句読記号を意図的に除外しており、そのため Halstead の合計は Tokens のカウントと 同じにはなりません

導出メトリクス

Halstead はここから数多くの公式を導出します。big-code-analysis は標準的なものすべてに加えて、元のスイートに含まれるあまり一般的でない 3 つの導出値(estimated_program_lengthpurity_ratiolevel)も報告します。

vocabulary               n  = n1 + n2
length                   N  = N1 + N2
estimated_program_length N̂  = n1·log2(n1) + n2·log2(n2)
purity_ratio                = N̂ / N
volume                   V  = N · log2(n)                          (bits)
difficulty               D  = (n1 / 2) · (N2 / n2)
level                    L  = 1 / D
effort                   E  = D · V          (elementary mental discriminations)
time                     T  = E / 18                               (seconds)
bugs                     B  = E^(2/3) / 3000 (estimated delivered defects)

これらの数値定数は、FORTRAN、PL/I、Algol 系言語を含む CDC 時代の異種プログラム群に対する Halstead の経験的フィッティングに由来します。T = E / 18 の「Stroud 数」は別系統で、心理学に由来します。Halstead は、人間の頭脳が 1 秒あたり約 18 回の基本的な弁別を行うという John Stroud の推定を借用しました。

読み方

Halstead の 本来の 意図は、プログラムが書かれる前に 3 つのことを予測することでした。すなわち、ビット単位でどれだけの大きさになるか、実装にどれだけの時間がかかるか、デプロイ後にどれだけのバグが見込まれるか、です。ボリュームと長さの予測については経験的な裏付けがそれなりにありますが、時間とバグの予測には議論の余地があり、特に Purdue のテクニカルレポート Software Science Revisited で詳細に批判されています。

現代の実践では、Halstead の数値は 3 つの用途に使われます。

  1. 複合メトリクスへの入力として。最も重要なのは、Halstead の ボリューム に依存する保守容易性指数(次節)です。
  2. 言語に依存しないサイズの代理指標として。ビット単位のボリュームは、LOC では実現できない形で言語をまたいで滑らかにスケールします。
  3. 相対的な工数見積もりのため。2 つのリファクタリング候補の循環的複雑度が同程度の場合、Halstead の difficulty が高いほうがリグレッションを持ち込む可能性が高くなります。

コード行数

この節では 5 つの LOC バリアント — SLOC、PLOC、LLOC、CLOC、BLANK — を扱います。「行を数える」ことは、何を数えるのかを正確に定義しなければならなくなるまでは簡単に聞こえます。以下の 5 つのバリアントは事実上の標準的な分類で、その起源は Samuel Conte、Hubert Dunsmore、Vincent Shen による 1986 年の教科書 Software Engineering Metrics and Models(Benjamin/Cummings、ISBN 0-8053-2162-4)に遡ります。この本が物理行と論理行の区別を体系化しました。Wikipedia の source lines of code の項目は、この物理行対論理行の区別を読みやすくまとめています。

バリアント数える対象
SLOCSource Lines Of Code — コメント・空行・コードを問わずファイル内のすべての行
PLOCPhysical Lines Of Code — 空行でもコメントのみの行でもない行
LLOCLogical Lines Of Code — 文を含む行(定義、代入、宣言)
CLOCComment Lines Of Code — コメントを含む行(同じ行にコードがあってもなくても数える)
BLANK空行 — 空白文字のみの行

アルゴリズム

big-code-analysis は、tree-sitter の構文木を 1 回走査するだけで 5 つのカウントすべてを導出します(src/metrics/loc.rs を参照)。コメントと文字列は字句走査ではなく AST(抽象構文木)のノード型で識別されるため、複数行文字列、raw 文字列、doc コメント、文字列補間はすべて正しく処理されます。言語ごとの Loc トレイトが、LLOC で「文」として数えるノード種別を規定します。何が文として数えられるかは言語ごとに定義されるため、ここが微妙な部分です。

5 つのカウントは、いくつかの有用な恒等式を満たします。

SLOC = PLOC + BLANK + (lines that are comment-only)
CLOC ≥ (lines that are comment-only)        # CLOC also counts mixed code+comment lines

読み方

  • SLOC は、多くの人が口語的に「コード行数」と言うときに指すものです。標準的なサイズの代理指標ですが、フォーマットの影響を受けやすく、言語の慣習をまたいだ移植性はありません。
  • PLOC は視覚的なノイズを取り除いたものです。後述の保守容易性指数の公式の内部で使われるサイズ指標です。
  • LLOC は最も信頼できる の数です。文ごとのテストケースを見積もる場合や、Python ファイルと Java ファイルの密度を比較する場合に適した指標です。
  • CLOC は PLOC と組み合わせることで コメント密度 が得られます。CLOC / PLOC は、ファイルのどれだけがドキュメントでどれだけが実装かを示す、大まかながら有用な代理指標です。
  • BLANK は主に診断用です。BLANK の割合が非常に低いファイルは、読みにくいことが多いためです。

LOC バリアントの発展的な用途は、単なるサイズの測定を大きく超えています。コスト見積もりモデルへの最も一般的な入力であり(COCOMO も COCOMO II も KSLOC — ソース行の千行単位 — を基本単位としています)、製品ポートフォリオのダッシュボードにおける工数予測にも使われ、さらに他のほぼすべてのメトリクスを正規化する分母として使われます。KSLOC あたりの欠陥数KSLOC あたりのチャーンKSLOC あたりのテストケース数 などです。弱点 — LOC は容易に操作でき、コーディングスタイルの 10 倍の違いが LOC の 2 倍の違いを生み得ること — こそが、この章にこれほど多くの他のメトリクスがある理由です。

保守容易性指数(MI)

保守容易性指数は、前述のメトリクスのいくつかを 1 つの 0〜100 程度の数値にまとめ上げ、「このコードはどれだけ保守しやすいか」として読むことを意図した複合メトリクスです。Paul Oman と Jack Hagemeister が 1992 年の ICSM 論文 Metrics for assessing a software system's maintainability で提案し、Don Coleman、Dan Ash、Bruce Lowther、Paul Oman が 1994 年の IEEE Computer 論文 Using metrics to evaluate software system maintainability(IEEE Computer 27(8)、44-49 ページ)で改良しました。彼らの方法論は経験的なものでした。Hewlett-Packard の少数の本番システムについて専門家による保守性評価を収集し、それぞれに 40 個の候補メトリクスを計算し、回帰分析に最良の線形結合を選ばせたのです。生き残った組み合わせは、Halstead ボリューム、循環的複雑度、コード行数、コメント密度を使うものでした。

big-code-analysis は、実践で定着した 3 つの公式を報告します。

3 つの値は mi オブジェクトの下に originalseivisual_studio というキーでネストされます(ドット区切りのしきい値名は mi.originalmi.seimi.visual_studio)。

mi.original      = 171 − 5.2·ln(HV) − 0.23·CC − 16.2·ln(SLOC)
mi.sei           = 171 − 5.2·log2(HV) − 0.23·CC − 16.2·log2(SLOC) + 50·sin(√(2.4·comment_percentage))
mi.visual_studio = max(0, mi.original · 100 / 171)
  • mi.original は Coleman–Oman の公式です。病的なファイルでは負になることがあります。
  • mi.sei は Software Engineering Institute による改良版で、コメント密度の項を追加しています。sin(√(...)) という形は、コメントが ある程度 は役立つものの、一定量を超えて増やしても効果がないように選ばれました。comment_percentage[0, 100] のパーセントで表したコメント行の割合であり([0, 1] の比率ではありません)、コードはこのパーセント値をそのまま SEI 項に与えます(src/metrics/mi.rs と issue #241 を参照)。
  • mi.visual_studio は Microsoft が Visual Studio 用に選んだ線形リスケーリングで、スコアは [0, 100] にクランプされ、信号機方式で開発者に表示されます。緑は 20 以上、黄は 10 以上、赤はそれ未満です。

このメトリクスの歴史的背景と鋭い批判は、Arie van Deursen のブログ記事 Think Twice Before Using the Maintainability Index にまとめられています。

アルゴリズム

実装は純粋な算術です。src/metrics/mi.rs は計算済みの HalsteadCyclomaticLOC メトリクスを受け取り、3 つの公式を適用します。公式が Halstead ボリュームと SLOC の自然対数を使うため、MI は空のファイルに対しては未定義です。big-code-analysis は、SLOC がゼロまたは Halstead ボリュームがゼロのファイルに対して 0.0 を返します。

読み方

MI は 本来 ポートフォリオレベルのスコアとして設計されました。「このコードベースから今後 1 年でどれだけの保守の苦労が見込まれるか」というものです。健全なシステムではリリースをまたいでかなり安定しており、システムが「レガシー」の象限に入る前に目に見えて低下する傾向があります。

発展的なユースケースは Visual Studio の信号機表示です。IDE でメソッドにカーソルを合わせたことのある C# 開発者は皆、緑・黄・赤のアイコンを見たことがあり、その背後にある数値が mi.visual_studio です。これにより MI は .NET 開発者の一世代にとって群を抜いて最もユーザーの目に触れるソフトウェアメトリクスとなり、それこそが最も多くの批判を集めたメトリクスである理由でもあります。サーモスタットではなく煙探知機として扱ってください。急激な低下は有用なシグナルですが、絶対値はノイズが多いのです。

NArgs

NArgs は、関数・メソッド・クロージャが宣言する引数の数を数えます。このメトリクスに有名な起源論文はありません。少なくとも Kernighan と Plauger の The Elements of Programming Style(1974 年)に遡る昔ながらの知恵であり、Robert C. Martin の Clean Code(2008 年)で目立つ形で再度述べられました。同書は 3 引数を緩やかな上限として提案しています。

big-code-analysis は、呼び出し可能要素の種類ごとにカウントを分割します。すべての集計値が 「関数」 と クロージャ で別々に報告されるため、|…| … クロージャの多い Rust ファイルと、メソッドしかない Java ファイルが比較可能な数値を生成します。シリアライズされた出力(src/metrics/nargs.rs)は function_argsclosure_argsfunction_args_averageclosure_args_averagetotalaveragefunction_args_minfunction_args_maxclosure_args_minclosure_args_max です。実装は、デフォルト引数、可変長引数、キーワード専用引数、分割代入されたパラメータを言語ごとに一貫して処理します。

Languages where it reads 0

A metric that silently reports 0 reads as "no offenders" rather than "not measured", so it is worth knowing where the count is inert:

  • Bash — correct and permanent. The shell has no formal parameter list; arguments arrive as $1, $2, and so on. It is the only language where every function reads 0.
  • Perl subs without a signature — correct. Signatures (sub add($x, $y)) are counted; a sub that reads its arguments from @_ declares no formal parameters to count.
  • Perl anonymous subs — a gap, and an upstream one: the grammar parses an anonymous sub's signature inside an error node, so my $f = sub ($x) {…} reads 0 even though it has a signature.

A nargs limit is therefore inert on a Bash codebase and sparse on Perl written before use v5.36. Gate it per language rather than repository-wide; see Choosing thresholds.

読み方

引数の多い関数は正しく呼び出すのが難しく、網羅的にテストするのはさらに困難です。テストマトリクスはおおよそ指数関数的に増大します。古典的なリファクタリングの助言は パラメータオブジェクトの導入 パターンです。関数が 4 つを超える関連した引数を取る場合、それらをレコード / 構造体 / データクラスにまとめます。

発展的な用途は、レビューをブロックする lint ルールとしてです。現代的なリンターの多く(pylintR0913、ESLint の max-params、Checkstyle の ParameterNumber)は、設定可能なしきい値を超える引数を持つ関数にフラグを立てます。NArgs は API 設計ダッシュボードの構成要素としても有用です。平均 NArgs が時間とともにじわじわ増えている公開 API は、「あともう 1 つだけパラメータを」という機能フラグを積み重ねてきた API であることが多いのです。

NExits

NExits は、関数からの相異なる出口点の数を数えます。すべての明示的な return、すべての throw / raise、そして(Rust では)すべての ? による早期リターンです。関数末尾の暗黙的なフォールスルーのリターンは数えません。明示的な出口のみが対象です(issue #243 を参照)。

このメトリクスは 1970 年代の構造化プログラミングの文献に遡ります。Edsger Dijkstra らは、関数は単一の入口と単一の出口を持つべきだと論じました(「SESE」ルール)。現代の考え方ははるかにニュアンスに富んでいます。Steve McConnell の Code Complete 第 2 版(Microsoft Press、2004 年)を参照してください。同書は、ネストを減らせる場合の 早期リターン を、明確さを高めるパターンとして明示的に推奨しています。

big-code-analysis は各関数の構文木を走査し、言語固有の出口ノードを特定し(src/metrics/nexits.rs の言語ごとの Exit トレイトを参照)、関数ごとのカウントに加えてファイルレベルの sumaverageminmax を報告します。シリアライズされたフィールド名は nexits で、本文で使っている略語表記と一致します。

読み方

厳格な SESE コーディング標準(アビオニクス向けの DO-178C、組み込み自動車向けの MISRA C — MISRA の公式サイト を参照)は、今でも関数あたりの NExits を 1 とすることを要求しています。複数の出口点は、認証対象の制御フロー解析を複雑にするためです。これらの領域の外では、NExits が 2-4 であることはたいてい 良い 兆候です。ほとんどの場合、その関数がガード節で事前条件を処理し、その後フラットな本体で処理を進めていることを意味するからです。

非常に 高い NExits — たとえば 8 超 — は警告のサインです。たいていの場合、その関数は複数の小さな関数に分割されるべきであり、それぞれの「成功する分岐」が独自のヘルパーになるべきだったことを意味します。

NOM

NOMNumber Of Methods(メソッド数)の略で、あるスコープ(ファイル、クラス、または名前空間)内で定義されたすべての関数・メソッド・クロージャを数えます。オブジェクト指向コードベースにとっては、Mark Lorenz と Jeff Kidd が 1994 年の著書 Object-Oriented Software Metrics(Prentice Hall、ISBN 0-13-179292-X)で最初期に導入したメトリクスの 1 つであり、同書では主要なクラスサイズ指標として扱われています。

big-code-analysis は src/metrics/nom.rs で、呼び出し可能要素の種類ごとに分割してカウントを報告します。シリアライズされるフィールドは functionsclosuresfunctions_averageclosures_averagetotalaverage(内包するスペース全体での平均)、および種類ごとの functions_minfunctions_maxclosures_minclosures_max です。

この分割により、同じコードに対して異なる問いを立てられます。クロージャが多く関数が少ない Rust クレートはイテレータ中心のコードに典型的であり、関数が多くクロージャが少ない Python モジュールはスクリプト風のコードに典型的です。

読み方

NOM は他のいくつかのメトリクスへの入力です。WMC は NOM が数えるのと同じメソッド集合に対して 循環的 複雑度を合計し、NPM は同じ集合を公開メソッドだけに絞り込みます。単独のメトリクスとしては、Lorenz–Kidd の推奨はクラスあたり ≤ 20 メソッドです。発展的な用途は God クラス検出器 としてです。NOM が数十に達するクラスはほぼ確実に多くを抱え込みすぎており、Martin Fowler の Refactoring カタログの Large Class の項目 に記述されている「コラボレーターの抽出」リファクタリングの有力候補です。

NPA

NPA は、クラスまたはインターフェイスが宣言する公開属性の数(フィールド、プロパティ、インスタンス変数とも呼ばれます)を数えます。Lorenz と Kidd が Object-Oriented Software Metrics(1994 年)で導入したメトリクスファミリーの一部であり、後に Brito e Abreu と Carapuça(1994 年) が提案した MOOD(「Metrics for Object-Oriented Design」)スイートに取り込まれました。

big-code-analysis は、定義箇所の種類ごとにカウントを分割します。クラス(状態を持つ具象型)と インターフェイス(抽象的な契約)です。シリアライズされた出力(src/metrics/npa.rs)は class_npa_sum(全クラスにわたる NPA の合計)、interface_npa_sum(インターフェイスにわたる合計)、class_attributes(公開か否かを問わない すべての 属性のクラスにわたる合計)、interface_attributesclass_cda(クラスの公開属性密度 — 平均ではなくアクセシビリティの 比率)、interface_cdatotaltotal_attributescda です。言語ごとの Npa トレイトが、何を「公開」と見なすか(Java の public、C# の public、Rust の pub、Python の「先頭にアンダースコアを付けない」慣習など)、そして何を「メソッド」ではなく「属性」と見なすかを決定します。

読み方

NPA はカプセル化の 直接的な 尺度です。すべての公開属性は、呼び出し側がメソッドを介さずに読み書きできる内部状態であり、つまりクラスが呼び出し側を壊さずには検証も進化もさせられない内部状態だということです。標準的な指針 — Bertrand Meyer の Object-Oriented Software Construction(Prentice Hall、1988 年)で最初に明示的に述べられ、* 統一アクセスの原則_ として知られています — は、NPA をゼロまたはその近くに保ち、代わりに公開メソッドを通じて状態を公開することです。

発展的な用途は API 安定性の監査です。時間とともに NPA が増えていく公開ライブラリのクラスは、公開メソッドの表面よりも速いペースで破壊的変更の負債を蓄積します。

NPM

NPM は、クラスまたはインターフェイスが宣言する公開メソッドの数を数えます。NPA のメソッド側の対になるもので、こちらも Lorenz と Kidd(1994 年)によって体系化されました。

NPA と同様に、big-code-analysis は NPM を定義箇所の種類(クラス対インターフェイス)で分割します。シリアライズされた出力(src/metrics/npm.rs)は class_npm_sum(クラスにわたる NPM の合計)、interface_npm_sumclass_methods(公開か否かを問わない すべての メソッドのクラスにわたる合計)、interface_methodsclass_coainterface_coa(平均ではなく操作アクセシビリティの 比率)、totaltotal_methodscoa です。言語固有の Npm トレイトが、何を公開と見なすかを決定し — たとえば Rust の pub、Python の先頭アンダースコアの慣習、C++ の public: セクション — 通常のメソッド、コンストラクタ、演算子オーバーロードを言語に応じて適切にまとめて扱います。

NPM は、Mark Hitz と Behzad Montazeri の Class Interface Size メトリクス や、Chidamber と Kemerer の Response For a Class(RFC)への入力の 1 つでもあります。

読み方

NPM は公開インターフェイスのサイズです。NPM が数十に達するクラスは、API の契約が大きすぎるクラスです。すべての公開メソッドは呼び出し側が依存し得るものであり、それに対するあらゆる変更は破壊的変更になります。Lorenz–Kidd の指針はクラスあたり公開メソッド ≤ 20 で、40 を超えるものは強力なリファクタリング候補と見なされます。同じルールは Java や C# の インターフェイス に特に強く当てはまります。そこでは契約こそが、クライアントが依存の拠り所とする形そのものだからです。

発展的な用途は、ライブラリ向けの公開 API 変更トラッカーとしてです。パッケージレベルで NPM を監視すると、NPA が内部フィールドの偶発的な露出を捉えるのと同じように、ライブラリの表面積の偶発的な拡大を捉えられます。

Tokens

Tokens は、tree-sitter のリーフトークン — 識別子、リテラル、キーワード、句読記号 — を関数ごと・ファイルごとに数えるカウントで、AST 上の祖先にコメントノードを持つトークンは除外されます。フォーマットの影響を受けにくい LOC の代替として意図された、現代的なレクサー駆動のサイズ代理指標です。(同じアイデアは Terry Yin の lizard コマンドラインツールでよく知られており、多くの読者がトークンカウントメトリクスを最初に目にするのはそこでしょう。)

実装は src/metrics/tokens.rs にあります。Tokens は Halstead が意図的に飛ばす句読記号を含む すべての リーフを数えるため、値は Halstead の N1 + N2 とは一致しません。また、行ではなくトークンを数えるため、どの LOC バリアントとも等価では*「ありません」*。空白のみの再フォーマットでは Tokens は変化しません。変数のリネームでもカウントは変化しません。コメントの削除でも Tokens は変化しません。トークンそのもの を変える編集 — if の追加、単一文ブロックへの省略可能な波括弧の追加、セミコロンが省略可能な言語でのセミコロンの挿入・削除 — はカウントを変化させます。

読み方

Tokens はこのスイートで最もフォーマットの影響を受けにくいサイズ代理指標です。言語をまたいで、あるいはスタイル規約の異なるチームをまたいで別のメトリクスを正規化する際に使うべきサイズ指標です。bugs per KSLOC はフォーマットの影響を受けますが、bugs per 1000 tokens ははるかに受けにくいのです。

発展的な用途は、言語横断的な研究における欠陥密度の分母の定番としてです。1000 行の Java ファイルと 1000 行の Lisp ファイルに含まれるコード量は大きく異なりますが、それぞれの 1000 トークン のスライスにはおおよそ同量の情報が含まれます。このため Tokens は、多くの言語にまたがって学習する機械学習ベースのコード品質モデルに特に有用です。

WMC

WMCWeighted Methods per Class(クラスあたりの重み付きメソッド数)— は、Chidamber と Kemerer のスイート の最初のメトリクスで、1994 年の IEEE Transactions on Software Engineering 論文 A Metrics Suite for Object Oriented Design(20 巻 6 号、476-493 ページ)で導入されました。CK スイート — WMC、DIT、NOC、CBO、RFC、LCOM — は、学術文献で最も引用されている OO メトリクス集です。big-code-analysis は現在 WMC と、より単純なサイズメトリクス(NOM、NPA、NPM)を実装しており、継承ベース・結合ベースのものは今後の課題として追跡されています。

WMC はクラス内で定義されたすべてのメソッドの循環的複雑度の合計です。原論文は「重み付け」を意図的に抽象的なままにしました。Chidamber と Kemerer は「すべてのメソッドの複雑度を 1 と見なすなら、WMC = n、すなわちメソッド数になる」と書いています。しかし、その後の経験的な追試文献はほぼ例外なく循環的複雑度を重みとして採用しており、big-code-analysis もそれを使用します。

アルゴリズム

言語ごとのパーサーが見つけた各クラスまたはインターフェイスについて、big-code-analysis はその内部のすべてのメソッド本体の標準的な循環的複雑度を合計します(src/metrics/wmc.rs)。ファイルレベルのシリアライズされた出力は 3 つのフィールドです。class_wmc_sum(ファイル内の全クラスにわたる WMC の合計)、interface_wmc_sum(インターフェイスにわたる合計)、total(両者の合計)です。ファイルスコープでは min/max/average の集計は出力されません。個々のクラスを WMC でランク付けするには、Type hotspots (top N by WMC) セクションを表示する report サブコマンドを使ってください(Commands → Report を参照)。

読み方

Chidamber と Kemerer は WMC について 3 つの仮説を提示し、そのすべてがその後繰り返し検証されてきました。

  1. WMC が高いほど保守工数の増加を予測します。 個々のメソッドが複雑なクラスは、理解しにくくなります。
  2. WMC が高いほど再利用が減ります。 多くの複雑なことを行うクラスは、新しいコンテキストに投入しにくいのです。
  3. WMC が高いほどアプリケーション固有の振る舞いが広いことを示唆します。 そのようなクラスは、再利用可能な部品ではなく「メインループ」型のコーディネーターになりがちです。

発展的な用途は God クラス検出です。NOM と組み合わせると、WMC はクラスを分割すべきだという最も明確なシグナルの 1 つになります。NOM が高く WMC が低いクラスは受動的なデータホルダーです(おそらく問題ありません)。NOM が低く WMC が高いクラスは、少数の巨大なメソッドを持っています(クラスではなくメソッドを分割してください)。NOM と WMC の 両方 が高いクラスが、古典的な God クラスです。


次のステップ

  • 対応言語 の章では、対応しているすべての言語と文法を一覧しています。一部のメトリクス定義(NPANPMWMC)はクラスを持つ言語でしか意味をなさないため、メトリクスの対応範囲は言語によって異なります。
  • 対応する変更履歴(VCS)メトリクス の章では、ソースの AST ではなくバージョン管理履歴から導出される補完的なファミリー — コミット頻度、チャーン、所有権、複合的なリスクスコアとホットスポットスコア — を扱います。
  • Commands → Metrics のページでは、これらの数値の JSON / YAML / TOML / CBOR 出力を生成するための bca metrics の呼び出し方を説明しています。
  • レシピ の章では、これらのメトリクスから品質レポートを生成するエンドツーエンドの例を示します。ダッシュボードへのパイプライン連携も含みます。