しきい値の選択

bca check は各メトリクスを、設定した制限と比較します。このページでは、出荷されるデフォルト値の由来、ゲート対象の言語に合わせた調整方法、そして別の用途に向けた別のセットの選び方を説明します。手作業かコーディングエージェント経由かを問わず、[thresholds] テーブルを編集するすべての人に向けたページです。

まだゲートを設定していない場合は、仕組みについてはローカルしきい値ゲートから、すでに抱えている違反者の吸収についてはベースラインから始めてください。このページは数値そのものを扱います。

出荷時のデフォルト

bca init はこのテーブルをスキャフォールドします。定義は big-code-analysis-cli/src/default_thresholds.rs の 1 箇所だけにあります。

メトリクス上限スコープアンカー
cognitive15関数SonarSource のデフォルト
cyclomatic15関数lizard のデフォルト。MISRA と NASA のセーフティクリティカル上限
abc40関数RuboCop AbcSize の 17 と、Flog の「60 以上は危険」の中間
nargs5関数RuboCop ParameterLists の 5。Code Climate はより厳しい 4
nexits5関数Code Climate return-statements の 4
halstead.effort50000関数公表値なし。パーセンタイルから導出
loc.ploc600ファイル公表値なし。パーセンタイルから導出
loc.sloc1200ファイル肥大化の歯止めであり、実働の制限ではない
nom30コンテナCode Climate method-count の 20。PMD TooManyMethods の 10
wmc60コンテナ公表値なし。パーセンタイルから導出

これらを読むときはスコープが重要です。cognitive の制限は各関数に適用されます。nomwmc は各クラス・struct・trait・impl・名前空間・インターフェイスに適用されます。loc.* はファイル全体のルートに適用されます。bca check がコンテナのメソッド数を関数単位の制限と比較することはありません。スコープ規則の全体は Check を参照してください。

これらの制限のうち 2 つは、その理由を明示しておく価値があります。

loc.plocloc.sloc は対になっています。PLOC は空行とコメントを除いた物理コード行数を数えるので、実働のファイルサイズ制限です。判断の経緯を文書化してファイルが伸びても、この制限に対するコストはゼロです。SLOC はすべてを数えるため、はるかに緩い位置に置かれ、仕事は 1 つだけです。それは、loc.ploc をクリアしたまま、コメント量によってファイルが際限なく成長するのを止めることです。ファイルサイズを SLOC だけでゲートすると、根拠の説明 1 段落にも新しい分岐 1 つと同じ代償を課すことになり、観測される結果は、人々がその根拠説明を削除することです。

cyclomatic を McCabe の 10 ではなく 15 とするのは意図的な緩和です。10 を標準的な数値として定着させた文書である NIST 500-235 は、それを正当化できるプロセスを持つチームには引き上げを認めており、15 という高さの制限も成功裏に使われてきたと述べています。実際のコードで測定すると、10 は 15 のおよそ 2 倍の関数をフラグしますが、レビュアーが問題と呼ぶものが相応に増えるわけではありません。

デフォルト値の導出方法

公表されているしきい値は互いに大きく食い違っています。循環的複雑度だけを見ても、一般的なツールのデフォルト値は 7(RuboCop)から 30(gocyclo)まで幅があり、Checkstyle と PMD は 10、lizard と MISRA は 15、ESLint は 20 です。権威に基づいて 1 つを選ぶことは、権威を選ぶことに他なりません。

そのため、ここでの各制限は測定に対しても検証されています。参照コーパスは 20 言語にわたる 43 の実世界リポジトリで、2026-07-31 にデフォルトブランチでクローンし、テストツリー、ベンダーコード、生成ファイルを除外したものです。bca metrics で測定すると、およそ 25 万の関数スペース、4 万のコンテナスペース、2 万 7 千のファイルになります。値は bca check が適用するのと同じ File、Function、Container のスコープで区分されるため、コンテナの nom が関数ごとの分布に混入することはありません。

設計ルールは、デフォルト値が中央値の言語でおよそ最悪 1% 〜 3% のスペースをフラグすべき、というものです。それを下回ると制限は機能せず、誤った安心感を与えます。それを上回るとゲートはゲートでなくなり、人々が迂回するスタイルルールになります。これは、70・80・90 パーセンタイルでリスク帯を導出する Alves, Ypma, and Visser のベンチマークアプローチをより厳格に読み替えたものです。彼らの 90 パーセンタイルは「見る価値がある」ラインとしては有用ですが、ビルドを失敗させるにはノイズが多すぎます。

従来のデフォルト値のうち 2 つはこのルールを満たさず、その結果変更されました。7 という nargs は中央値の言語で関数の 1% 未満にしか発火せず、このプロジェクト自身のソースでは何にも発火しなかったため、何も捕捉できない制限になっていました。(このコーパス測定は、nargs を関数自身のパラメータに限定した #1196 より前のものです。再測定すれば数値は「下がり」、クロージャの多い言語ほど大きく下がるため、「何も捕捉できない」という結論はなおさら成り立ちます。)25 という cognitiveclippy から継承されたもので、clippy はゲートではなくリントを行うため意図的に保守的です。15 はこのメトリクスの設計者たちが選んだ値です。

言語ごとのオーバーライド

メトリクスの分布は、プロジェクトよりも言語によって大きく変わります。参照コーパスにおける関数ごとの cognitive の 90 パーセンタイル値は、C# の 0 から Tcl の 21 まで広がっています。ファイルの loc.ploc の中央値は TypeScript の 16 から C の 283 までで、18 倍の開きがあります。1 つの表で両端をカバーすることはできません。

以下の表は言語ごとに測定した 97.5 パーセンタイルを示します。これは、その言語のコードの最悪 2.5% をフラグするために制限が取るべき値です。処方箋ではなく較正データとして読んでください。数値が低いのはその言語のコードが概ね単純だということであり、引き締めることが「できる」理由ではあっても、そうすべきだという証拠ではありません。

言語cognitivecyclomaticabchalstead.effortloc.ploc
Bash35256095000(サンプル不足)
C5030602000003500
C++14102550000(サンプル不足)
C#44139500700
Elixir5720150001500
Go2517451200001500
Groovy16132530000450
Java751615000800
JavaScript20153555000(サンプル不足)
Kotlin1082020000300
Lua35204085000800
Objective-C18123560000(サンプル不足)
Perl3525451400001500
PHP1092550000900
Python20132525000700
Ruby761910000400
Rust882035000900
Tcl45258590000(サンプル不足)
TypeScript20132555000450
TSX14106595000400

「サンプル不足」は、コーパス内のファイル数が少なすぎてファイルサイズの数値を導出できない言語を示します。Bash、Tcl、JavaScript、Objective-C の関数ごとの数値は表の中で最も小さいサンプルに基づいており、自分のコードに対して再導出する価値が最も高いものです。

何を変えるか、そしてなぜ

ほとんどの言語にオーバーライドは不要です。必要になるケースは 3 つのグループに分かれます。

大きなディスパッチ関数を持つ手続き型言語。 C、Tcl、Bash、Lua、Perl、Go はいずれもデフォルト値の 2 〜 3 倍の値を示します。これらの言語には例外がないか、例外の使用が推奨されないため、エラー処理はインラインの分岐となり、ポリモーフィズムよりも長い switch スタイルのディスパッチが好まれます。デフォルトの cognitive 制限は、中央値の言語では関数の 3% であるのに対し、これらの言語では 5% 〜 15% をフラグします。まず cognitivehalstead.effort を引き上げてください。超過分の大半はこの 2 つが占めています。

単一言語のリポジトリでは、これは [thresholds] テーブル全体になります。混在リポジトリでは、対象とする言語の下に置くべきです。さもないと、他のすべての言語まで一緒に引き上げてしまいます。

# C のゲート設定。同じ形は Tcl、Bash、Lua、Perl、Go にも適しています。
[thresholds.lang.c]
cognitive = 30
cyclomatic = 20
abc = 50
"halstead.effort" = 120000
"loc.ploc" = 1200
"loc.sloc" = 2000

モジュール構成要素がクラスではない言語。 nomwmc は Container スコープであり、コンテナとは文法がクラス、struct、trait、impl、namespace、interface と呼ぶものすべてを指します。Elixir の defmodule は設計上何十もの関数を保持するため、Elixir モジュールのおよそ 3 分の 1 が nom = 30 に違反します。これはスコープの不一致であって、コードスメルではありません。Rust は反対側の端に位置します。impl ブロックは通常わずかなメソッドしか持たないため、デフォルト値が発火することはありません。

# Elixir modules and Rust impls sit at opposite ends of the same metric.
[thresholds.lang.elixir]
nom = 150
wmc = 300

[thresholds.lang.rust]
nom = 20
wmc = 40

コンパクトで高度に抽象化された言語。 Java、Ruby、Kotlin、C#、Elixir は、関数ごとのすべてのメトリクスでデフォルト値を大きく下回ります。97.5 パーセンタイルの cyclomatic は 4 〜 8 です。これらを 15 でゲートすると事実上一度も発火しないため、ゲートを機能させたいなら引き締めてください。

C# の行は注意して読んでください。その分布はプロパティアクセサに支配されています。bca はこれらを関数として数えますが、ほぼ常に自明なため、パーセンタイルはレビュアーが実際に見ることになるコードよりも低く出ます。同じ効果は Java、Kotlin、Ruby にも弱く現れます。これら 4 行をそのまま採用するのではなく、自分のリポジトリから再導出することを推奨します。自分のコードベースに対する再導出を参照してください。

すべての言語に適用されるわけではないメトリクス

nargs はすべての Bash 関数に対して 0 を報告しますが、これは正しく、恒久的な動作です。シェルには形式的なパラメータリストがなく、引数は $1$2 などとして渡されるため、この制限は単に機能しません。したがって Bash のコードベースでは nargs 制限は無条件に通過し、それは「測定されていない」ではなく「違反なし」と読めてしまいます。この状態に残っている言語は Bash だけです。

Perl はカウントされますが、ソースがシグネチャを宣言している場合(sub add($x, $y)。5.20 以降で安定し、use v5.36 の下ではデフォルトで有効)に限られます。シグネチャの「ない」sub は引数を @_ から受け取り、測定すべき形式的なパラメータリストを持たないため、0 と読み取られます — これは正しい動作です。したがって use v5.36 以前のコードベースでは Perl の nargs 分布は疎になり、また匿名 sub の 1 つの形式は、文法がそのシグネチャを誤って解析するため依然として 0 と読み取られます。

wmc, npm, and npa are only produced for languages with a class-like container. They are absent from Bash, C, Lua, Perl, and Tcl output. Go is a half case: it has no container space at all, so wmc is absent and npm / npa are reported only on the file unit root. bca check gates both on container spaces and never on the root, so no npm or npa limit can fire on Go source — see Which spaces carry NPA, NPM and WMC.

言語ごとのオーバーライドではこれらのいずれも解決できません。Bash の nargs はすべての関数で 0 であるため、どんな制限を設定しても — 値がそれを「上回った」ときにのみ発火する 0 でさえも — ゲートに何かを言わせることはできません。オーバーライド機構は制限を調整するものであり、文法が供給できない測定値を追加するものではありません。

オーバーライドの適用

bca.toml[thresholds.lang.<slug>] テーブルとして記述します。各テーブルはメトリクスごとにプロジェクトの [thresholds] の上に重ねられるため、言語は再指定しなかったすべての制限を継承します。

[thresholds]
cognitive = 15
cyclomatic = 15
nargs = 5
nom = 30
wmc = 60

# 手続き型でディスパッチが多い言語: 超過分を占める 2 つのメトリクスを引き上げます。
[thresholds.lang.c]
cognitive = 30
cyclomatic = 20
"halstead.effort" = 120000
"loc.ploc" = 1200

# defmodule はクラスではないため、nom と wmc にはモジュール規模の制限が必要です。
[thresholds.lang.elixir]
nom = 150
wmc = 300

# コンパクトで高度に抽象化された言語: 引き締めないとゲートは一度も発火しません。
[thresholds.lang.csharp]
cognitive = 8
cyclomatic = 8

キーは正規の言語スラッグで、--language が受け付けるのと同じ語彙です(cppcsharpobjctsxmozcppmozjs)。未知のスラッグは静かな no-op ではなくツールエラーになります。専用のテーブルを持たない言語は [thresholds] でゲートされます。--tier=soft では、各言語のソフト帯は「その言語自身の」解決済み制限から導出されるため、制限を引き上げた言語のソフト帯は、プロジェクト全体の数値ではなく引き上げ後の数値からスケールされます。--print-effective-config が解決済みテーブルをどのように表示するかを含む完全なリファレンスは bca check にあります。

テーブルをこのように分割する際に注意すべき点が 2 つあります。.h は C ではなく C++ の文法で解析されるため、C プロジェクトのヘッダーは [thresholds.lang.cpp] でゲートされます。拡張子の対応表は対応言語を参照してください。また、コマンドラインの --threshold はグローバルのままで、言語ごとのテーブルもすべて上書きします。これは一回限りの実行では望ましい挙動ですが、組み合わさることを期待していた場合には驚きになります。

代替手段は今でも利用できますが、依然として劣ります。最も緩い言語に合わせた単一のテーブルを維持し、残りをファイルごとのベースラインに任せる方法です。維持は簡単になりますが厳密に弱くなります。より厳格であるべき言語がまったくゲートされなくなるからです。

ユースケース別プロファイル

同じコードベースでも、ゲートの目的によって求められる制限は異なります。

ブロッキング CI ゲート

同梱のデフォルトはこのためのものです。プルリクエストを失敗させる制限は、チームが本当に問題だと合意できるものでなければなりません。偽陽性のコストがマージのブロックと論争になるからです。ベースラインと組み合わせて、ゲートが新規の違反とリグレッションでのみ失敗するようにし、そこから引き締めていきます。

同梱のデフォルトを変更せずに使い、さらに次を追加します。

[check]
baseline = ".bca-baseline.toml"

エージェントのフィードバックループ

利用者が人間のレビュアーではなくコーディングエージェントである場合、偽陽性のコストはマージのブロックではなく無駄なリファクタリング 1 回で済み、シグナルはコードを書いている最中に届きます。公表されている関数ごとの値に向けて引き締め、ファイルサイズとコンテナの制限は外してください。1 つの関数を編集しているエージェントには対処のしようがないからです。

[thresholds]
cognitive = 10
cyclomatic = 10
abc = 25
nargs = 4
nexits = 4
"halstead.effort" = 25000

これを編集ループに組み込む方法はエージェントにメトリクスを渡すを、複雑さが偶発的ではなく本質的である場合にそれをエージェントへ伝える方法は抑制マーカーを参照してください。

レガシー監査とトリアージ

目的が未知のコードベースをゲートすることではなくランク付けすることである場合、欲しいのは数千件のリストではなく、本当に最悪な一握りの関数です。極端な外れ値だけが浮かび上がるよう制限をデフォルトのおよそ 2 倍に設定し、ベースラインなしで実行し、bca check ではなく bca report markdown を使います。

[thresholds]
cognitive = 40
cyclomatic = 30
abc = 80
"halstead.effort" = 250000
"loc.ploc" = 1500
nom = 60
wmc = 120

レポート出力については品質レポートで説明しています。--vcs を追加すると変更履歴によるランク付けも行われ、通常は複雑さ単独よりも優れたトリアージ順序になります。3 年間誰も触れていない複雑な関数は、バグの在り処ではありません。

セーフティクリティカルおよび規制対象

この分野の標準は制限を直接規定しており、どのような測定よりも標準が優先されます。MISRA と NASA はいずれも循環的複雑度の上限を 15 とし、JSF C++ 標準は大きな switch 文に対する文書化された例外付きで 20 を許容します。McCabe のオリジナルの 10 は、テスト予算がそれを支えられる場合に適用されます。この数値はスタイルに関する意見ではなく、基底パステスト数の境界だからです。

[thresholds]
cognitive = 15
cyclomatic = 10
abc = 30
nargs = 5
nexits = 1
"halstead.effort" = 25000

nexits = 1 は MISRA C:2023 Rule 15.5 の単一出口ルールを符号化したものです。規制対象の業務以外では異論が多く、ほとんどのコードベースでは早期リターンはコードを追いにくくするどころかむしろ単純にするため、一般的な推奨ではなくコンプライアンス設定として扱ってください。

デフォルトではゲートされないメトリクス

これらは計算され、bca report markdown|html で確認できます。デフォルトのテーブルからは意図的に除外されています。

halstead.volume には、Halstead の測定値の中で最も広く引用されるしきい値があります。関数の volume は 1000 未満に保つべきだというガイドラインです。コーパスに対して測定すると、中央値の言語で関数の約 7%、最悪の言語で 20% にフラグが立ちます。つまりランク付けには有用でも、ゲートとしては使いものにならないということです。

mi.originalmi.seimi.visual_studio は Maintainability Index(保守性指標)のファミリーで、低いほど悪い指標です。違反となるのは制限を下回る値です。Visual Studio のバンド(10 未満は不良、10〜19 は中程度、20 以上は良好)は、同製品が 0〜100 に再スケールした独自の出力に適用されるもので、bca はこれを mi.visual_studio として報告します。mi.original は上に有界ではなくコーパス上で 167 に達するため、オリジナルの SEI の 65 と 85 のバンドはそのまま移せません。この指標はまた、すでにゲートしているメトリクスから計算される関数でもあるため、これもゲートすると二重カウントになります。数式は対応コードメトリクスを参照してください。

npmnpatokenscyclomatic.modified が省かれているのは、それぞれがテーブルに既にある何かと重複しているか、あるいはその分布が設計品質ではなく言語のイディオムに支配されているためです。

クラスターに向けた制限の引き締め

**比例型のソフト層が有効なあいだ、既存の値のクラスターへ制限を収束させることは決して無償ではありません。**無償だと結論する前に、候補を「両方の」層で測定してください。自然に目が向くのはハード層の読み値ですが、誤解を招くのはまさにそれです。

ソフト層は制限までの距離を測ります。したがって、母集団の値のちょうど上に置かれる制限は、構造上ソフト層の超過を最大化します。その値にある関数はすべて、制限が定まった瞬間にソフト帯の内側に入り、どれだけ整理しても外には出られません。その値こそが制限「そのもの」だからです。

本プロジェクトはこの罠に踏み込みました。nargs7 で、6 への引き締めは無償に見えました。より厳しい制限での違反はすべて既にベースラインに入っており、bca check --threshold nargs=6 は何も報告しなかったのです。

nargs の制限ハード違反ソフト制限(0.95ソフトの違反
7(維持)276.6560
6(提案)605.7134

74 個の関数がちょうど 6 にあります。制限を 6 にすると、その全員が一斉にソフト帯の境界を 0.3 超えた内側に置かれます — ハード層での利得ゼロと引き換えに購入された 74 件のベースラインエントリであり、シグネチャを書き換えない限りどれ一つとして退役させられません。制限は 7 のままとなり、誠実な代替案は本物の 6 → 5 の作業でした。(#1143#1169。)

上記の数値は #1196 より前、つまり nargs ゲートが関数自身のパラメータにネストしたすべてのクロージャの分を合算していた時期に測定されたものです。その母集団の大半はそもそもパラメータ過多ではありませんでした。制限 5 が新たにゲートしたはずの 76 関数のうち、自身のパラメータが 6 個以上あったのは 17 だけで、うち 1 つはパラメータ 1 個に本体内のクロージャが寄与した 5 個を加えたものでした。自身のパラメータのみをゲートする方式で同じツリーを再測定すると次のようになります。

nargs の制限ハード違反新規(ベースライン未登録)ソフトの違反
7(維持)60160
61603817
5381712999

クラスターの罠そのものは変わっておらず、それがこの節の存在理由です。現在は 91 個の関数がちょうど 5 にあり、制限 5 はやはりその全員を恒久的にソフト帯の内側に置きます。変わったのは、その背後にある実作業の規模です。混在したケースではなく、シグネチャそのものになりました。

この収束はその証拠に基づいて実施されました。測定によって明確になり、この節が以前は誤って述べていた点が 1 つあります。クラスター効果は、母集団の最頻値の上に着地することによる性質ではなく、あらゆる整数メトリクスにとって構造的なものだということです。ちょうど制限 L にある関数は常に 0.95 x L を上回るため、常にソフト帯の中にあります。ちょうど 7 にあった 10 個の関数は変更前からその中にあり、ちょうど 5 にある 91 個が変更後にその中にあるのと同じです。制限の選択が左右するのは、そこに「いくつ」入るかだけです。#1143 の異議はそのように読んでください。それが妥当だったのはハード層の利得がゼロだったからであって、6 が最頻値の近くにあったからではありません。

これはまた、0.95 x LL の差が 1 未満になる小さな整数のメトリクスでは、ソフト層が持つ情報が乏しいことも意味します。halstead.effort(50000 対 47500)では実質的な仕事をしていますが、nargs(5 対 4.75。上限ぎりぎりにある適合済みの関数すべてにフラグを立てます)ではほとんど何もしていません。

bca check --explain-threshold <metric>=<limit> は、bca.toml に触れることなく、1 回の走査で両方の層を測定し、クラスターを見つけた場合はそれを名指しします。

$ bca check --explain-threshold nargs=6
nargs: candidate limit 6
  hard tier (limit 6): 60 offenders, 60 already baselined, 0 new
  soft tier (limit 5.7, 0.95x): 135 offenders, 61 already baselined, 74 new
  cluster: 75 of 75 soft-band offenders sit at exactly 6 — the candidate limit itself. …

この罠はグローバルテーブルに限られません。言語ごとのオーバーライドも制限を母集団へ収束させるもう 1 つの方法であり、同じ崖を持っています。--explain-threshold は、独自の制限を維持している言語をカウントに畳み込まず、別の行として報告します。

この規則はソフト層を越えて一般化できます。クラスターの上に着地した制限は母集団に余地を残さないため、実際のコードを動かさなければならなくなるのは「次の」引き締めです。クラスターとクラスターの間隙にある制限を選んでください。このフラグの完全な契約については候補の制限をプレビューするを参照してください。

自分のコードベースに合わせた再導出

コーパスのパーセンタイルは出発点にすぎません。自分自身の分布のほうが優れた証拠であり、それを得るにはコマンド 1 つと短いスクリプトで足ります。

bca metrics --no-config -O json -I '*.rs' -X '**/tests/**' . > metrics.jsonl

各行が 1 ファイル分の FuncSpace ツリーです。これを走査し、関心のあるメトリクスについて各スペース自身の値を保持し、メトリクスのスコープに合わせて kind でフィルタし("function"loc.* には "unit"nomwmc にはコンテナの kind)、97.5 パーセンタイルを取ります。そこに制限を設定すると、最悪の 2.5% にフラグが立ちます。

その後に実行する価値のあるチェックが 2 つあります。まず、その制限が生む違反の数を数えてください。スペース全体の数パーセントを超えるなら、そのゲートは守られるのではなく無視されるでしょう。次に、値がどこに積み上がっているかを見てください。自然な分布は滑らかに裾を引くため、制限のすぐ下に張り付いたファイルのクラスターは、制限がコードを測定するのではなく形作っており、人々が収まるように切り詰めていることを意味します。これは本プロジェクト自身の loc.sloc ゲートで実際に起こり、issue #1138 に記録されています。

bca check --write-baseline は今日時点の違反を記録するため、初日から失敗することなく、より厳しい制限を採用できます。ブートストラップ、更新、退役のフローはベースラインで説明しています。