じゃあ、おうちで学べる

本能を呼び覚ますこのコードに、君は抗えるか

cargo-coupling: Rustプロジェクトの結合度を可視化する

cargo-coupling を自己診断した時のweb ui です。

はじめに

「このモジュール、なんか触りたくないな...」

ソフトウェア開発をしていると、こんな感覚を覚えることがあります。変更するたびに他の箇所が壊れる、テストが書きにくい、そもそも何をしているのか把握しづらい。これらの症状には共通点があります。モジュール同士が過剰に依存し合っている、つまり結合(カップリング)の問題です。

結合の問題は厄介です。コードを書いているときには気づきにくく、後から「なぜこんなに変更が大変なのか」と悩むことになります。さらに困るのは、「結合が強すぎる」と分かっても、具体的にどこがどう強いのか、どこから手をつければいいのかが見えにくいことです。

振り返ってみると、私は結合に対する解像度がかなり低かったのではないでしょうか。「なんとなく密結合っぽい」「疎結合の方がいいらしい」という感覚で良し悪しを判断していた。でも、その感覚を言葉にしようとすると、うまく説明できない。

この「見えにくさ」を解消するには、結合を測る物差しが必要です。しかし、従来の「強い/弱い」という1軸だけでは不十分でした。なぜなら、同じ「強い結合」でも、場所や状況によって意味が変わるからです。

そこで注目したいのが、Vlad Khononovの「Balanced Coupling」という考え方です。結合を「強度」「距離」「変動性」の3つの軸で捉え、それらのバランスを評価するフレームワークです。今回紹介するcargo-couplingは、このフレームワークをRustプロジェクト向けに実装したツールです。

AIがコードを書く時代になっても、この結合度という指標は重要性を増すはずです。なぜなら、コードを書く主体が誰であれ、そのコードを理解し、保守し、拡張するのは人間だからです。むしろAIが生成したコードだからこそ、その構造を客観的に評価できる物差しが必要になります。

まずはツールの概要を見てから、その背景にある考え方、そして実際の使い方へと進んでいきましょう。

cargo-couplingとは

cargo-couplingは、私がRustプロジェクト向けに開発した結合度分析ツールです。

このツールを作るきっかけになったのは、Vlad Khononovの著作「Balancing Coupling in Software Design」との出会いでした。結合設計について漠然と感じていた課題が、この本で体系的に整理されていたのです。「強度」「距離」「変動性」という3つの軸で結合を捉えるフレームワークに感銘を受け、これをRustプロジェクトで実際に使えるツールにしたいと考えました。書籍は翻訳も含めて読みやすいので、ぜひ手に取ってみてください。

ツールはGitHubで公開しています。気に入ったらStarしていただけると励みになります。

github.com

crates.ioからインストールできます。

https://crates.io/crates/cargo-couplingcrates.io

ここで、多くの人が持っている常識を一度疑ってみましょう。

「結合は減らすべきだ」——そう思っていませんか?

このツールは「結合を減らす」ことを目標にしていません。「結合を適切に設計する」ことを目標にしています。なぜなら、結合は本質的に悪ではないからです。関連する機能が密に連携するのは自然なことで、問題になるのは「不適切な場所での強い結合」や「遠く離れたモジュール間の密結合」です。この視点の転換が、このツールの核心です。

# インストール
cargo install cargo-coupling

# 基本的な使い方
cargo coupling ./src

3つの次元で結合を分析

では、「適切な結合」とは具体的に何を指すのでしょうか。

従来の結合分析は「強い/弱い」の1軸で考えがちでした。しかし、ここで立ち止まって考えてみてください。同じ「強い結合」でも、すぐ隣のモジュールとの結合と、遠く離れた外部ライブラリとの結合では、意味が違うはずです。また、5年間ほとんど変更されていないコードとの結合と、毎週のように変更されるコードとの結合では、リスクが違うはずです。

この違いを捉えるには、1軸では足りません。cargo-couplingは結合を3つの独立した次元で測定します。

1. Integration Strength(結合強度)

最初の軸は「結合強度」です。モジュール同士が「どれだけ互いの内部を知っているか」を表します。

user.password_hashのように構造体のフィールドを直接触っているコード、見覚えがありませんか?これは最も強い結合です。一方、impl Traitを介してやり取りするコードは、相手の内部を知らなくても動作します。この違いをスコア化します。

レベル スコア 説明 Rust例
Intrusive 1.00 内部実装に直接依存 struct.field 直接アクセス
Functional 0.75 関数シグネチャに依存 メソッド呼び出し
Model 0.50 データ構造に依存 型定義、型パラメータ
Contract 0.25 trait/インターフェースのみ impl Trait

2. Distance(距離)

2つ目の軸は「距離」です。結合されたモジュール同士が、コードのスコープ階層でどれだけ離れているかを表します。

同じファイル内の関数同士が密に連携しているのは自然なことです。しかし、src/auth/login.rssrc/billing/invoice.rsを直接参照していたらどうでしょう?さらに、外部クレートの内部構造に依存していたら?距離が遠いほど、その結合の「重さ」は増します。

レベル スコア 説明
SameModule 0.25 同一ファイル/モジュール内
DifferentModule 0.50 同一クレート内の別モジュール
DifferentCrate 1.00 外部クレートへの依存

3. Volatility(変動性)

3つ目の軸は「変動性」です。「どれくらい頻繁に変更されるか」を表します。

あなたのプロジェクトにも、1年以上触られていない安定したモジュールと、毎週のように修正が入るモジュールがあるはずです。安定したコードに依存するのと、頻繁に変わるコードに依存するのでは、リスクが違います。cargo-couplingはGit履歴からこの変動性を自動で計算します。

レベル スコア Git 6ヶ月での変更回数
Low 0.00 0-2回
Medium 0.50 3-10回
High 1.00 11回以上

バランススコアの計算

ここまで3つの次元を見てきました。しかし、「強度が0.75」「距離が0.50」「変動性が中程度」とバラバラに言われても、結局この結合は良いのか悪いのか、判断しづらいですよね。

そこでcargo-couplingは、これら3つの次元を組み合わせてバランススコアを計算します。3つの数値を1つのスコアにまとめることで、「この結合は適切か」を直感的に判断できるようになります。

考え方はシンプルです。「強度と距離のバランス」と「変動性によるリスク」の2つを掛け合わせます。

ALIGNMENT = 1.0 - |STRENGTH - (1.0 - DISTANCE)|
VOLATILITY_IMPACT = 1.0 - (VOLATILITY × STRENGTH)
BALANCE_SCORE = ALIGNMENT × VOLATILITY_IMPACT

最初の式は「強度と距離が釣り合っているか」を測ります。距離が近ければ強結合でも問題なく、距離が遠ければ弱結合であるべきです。2番目の式は「変更頻度と結合強度の組み合わせリスク」を測ります。頻繁に変更されるコードと強く結合していると、変更のたびに影響を受けるリスクが高まります。

この計算式が導く結論を整理すると、以下のようになります。

  • 強結合 + 近距離 → Good:関連機能が1つのモジュールにまとまった高凝集な状態
  • 弱結合 + 遠距離 → Good:モジュール間の依存が最小限な疎結合アーキテクチャ
  • 強結合 + 遠距離 → Bad:変更影響が広範囲に及ぶグローバル複雑性の状態
  • 強結合 + 高変動性 → Bad:頻繁な変更が連鎖的影響を生む変更波及リスク

実際に使ってみる

理論を理解したところで、実際のプロジェクトでどう使うかを見ていきましょう。cargo-couplingは目的に応じて複数の出力形式を用意しています。

サマリー表示

cargo coupling --summary ./src

出力例は以下のとおりです。

Coupling Analysis Summary:
  Health Grade: B (Good)
  Files: 14
  Modules: 14
  Couplings: 389
  Balance Score: 0.83

  Issues:
    Medium: 2

  Top Priority:
    - [Medium] cargo-coupling::main → 21 dependencies
    - [Medium] 21 dependents → cargo-coupling::cargo_coupling

  Breakdown:
    Internal: 33
    External: 356
    Balanced: 33
    Needs Review: 0
    Needs Refactoring: 0

  Connascence:
    Total: 807 (avg strength: 0.23)
    High-strength: Position=2, Algorithm=2

  APOSD Metrics:
    Pass-Through Methods: 12 (simple delegation)
    High Cognitive Load: 2 modules
    Avg Module Depth: 7.9

日本語出力

日本語での出力も対応しています。

cargo coupling --japanese ./src
カップリング分析: my-project
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

評価: B (Good) | スコア: 0.67/1.00 | モジュール数: 14

3次元分析:
  結合強度: Contract 1% / Model 24% / Functional 66% / Intrusive 8%
           (トレイト)   (型)      (関数)        (内部アクセス)
  距離:     同一モジュール 6% / 別モジュール 2% / 外部 91%
  変更頻度: 低 2% / 中 98% / 高 0%

ホットスポット分析

リファクタリングすべき優先度の高いモジュールを特定します。

cargo coupling --hotspots ./src
#1 my-project::main (Score: 55)
   🟡 Medium: High Efferent Coupling

   💡 What it means:
      This module depends on too many other modules

   ⚠️  Why it's a problem:
      • Changes elsewhere may break this module
      • Testing requires many mocks/stubs
      • Hard to understand in isolation

   🔧 How to fix:
      Split into smaller modules with clear responsibilities
      e.g., Split main.rs into cli.rs, config.rs, runner.rs

影響分析

特定のモジュールを変更したときの影響範囲を調べられます。

cargo coupling --impact metrics ./src

Web UIでの可視化

インタラクティブなグラフで結合関係を可視化できます。

cargo coupling --web ./src

ブラウザが自動で開き、Cytoscape.jsを使った対話的なグラフが表示されます。ノードをクリックすると詳細情報が見られ、問題のあるモジュールは色分けされます。

CI/CDでの活用

手動で分析するだけでなく、継続的に品質を監視することもできます。cargo-couplingを品質ゲートとして組み込むと、結合設計の劣化を早期に検出できます。

cargo coupling --check \
  --min-grade=B \
  --max-circular=0 \
  ./src

GitHub Actionsの例は以下のとおりです。

- name: Check coupling health
  run: |
    cargo coupling --check \
      --min-grade=B \
      --max-critical=0 \
      ./src

グレードが基準を下回るとexit code 1を返すため、CIパイプラインに組み込めます。

AI連携

Claude CodeやGitHub Copilotと組み合わせて使う場合、--aiオプションが便利です。

cargo coupling --ai ./src

AIフレンドリーな形式で出力されるので、そのままAIに貼り付けてリファクタリング提案を得られます。

検出される問題パターン

ここまで使い方を見てきましたが、具体的にどんな問題が検出されるのか気になるところでしょう。cargo-couplingが警告する代表的なパターンを紹介します。

God Module(神モジュール)

関数、型、implが多すぎるモジュールです。

  • 関数: 30個以上
  • 型: 15個以上
  • impl: 20個以上

High Efferent Coupling(外向き結合過多)

依存先が多すぎるモジュール。デフォルトでは20以上の依存で警告されます。

High Afferent Coupling(内向き結合過多)

依存されすぎているモジュール。デフォルトでは30以上の依存元で警告されます。

Cascading Change Risk(変更波及リスク)

侵入的結合(Intrusive)と高変動性(High Volatility)の組み合わせ。変更のたびに広範囲に影響が及ぶ危険な状態です。

ヘルスグレードの解釈

問題パターンの検出結果は、最終的に1つのグレードに集約されます。このグレードがプロジェクト全体の健全性を示します。

Grade 説明
S Over-optimized。リファクタリングしすぎかも
A Well-balanced。理想的な状態
B Healthy。管理可能な状態
C 改善の余地あり
D 注意が必要
F 即刻対応が必要

興味深いのは、Sグレードが「やりすぎ」とされている点です。なぜでしょうか?

結合を減らしすぎると、コードが細切れになりすぎて、かえって全体像が見えなくなります。1つの処理を追うために10個のファイルを開く必要があったり、抽象化のレイヤーが深すぎて「結局何をしているの?」と迷子になったり。そういう経験はありませんか?

結合は「減らせばいい」という単純な話ではありません。バランスが大切なのです。

ライブラリとしての利用

CLIツールとして使うだけでなく、独自のツールに組み込むこともできます。cargo-couplingはライブラリとしても公開しているので、プログラムから直接分析機能を呼び出せます。

use cargo_coupling::{
    analyze_workspace,
    analyze_project_balance_with_thresholds,
    IssueThresholds,
    VolatilityAnalyzer,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // AST解析
    let mut metrics = analyze_workspace(Path::new("./src"))?;

    // Git変動性分析
    let mut volatility = VolatilityAnalyzer::new(6);
    volatility.analyze(Path::new("./src"))?;
    metrics.file_changes = volatility.file_changes;
    metrics.update_volatility_from_git();

    // バランス分析
    let report = analyze_project_balance_with_thresholds(
        &metrics,
        &IssueThresholds::default()
    );

    println!("Grade: {}", report.health_grade);
    Ok(())
}

パフォーマンス

cargo-couplingは、大規模プロジェクトでも高速に動作するよう設計されています。

  • Rayonによる並列AST解析
  • Git履歴のストリーム処理
  • 実績: tokio(488ファイル)で655ms

--no-gitオプションを使えば、Git分析をスキップしてより高速に動作します。

制限事項

便利なツールですが、万能ではありません。使う前に知っておくべき制限があります。

  1. 外部クレート依存は分析対象外: serde、tokioなどへの依存は分析されない。開発者がコントロールできない部分のため
  2. 静的解析のみ: ランタイムの動作やマクロ展開は完全には捉えられない
  3. Git履歴が必要: Volatility分析にはGit履歴が必要。履歴が短いと精度が下がる

まとめ

cargo-couplingは、「結合は悪」という単純な考え方ではなく、「適切な結合を選ぶ」という実用的なアプローチを提供します。

  • 3次元分析: 強度・距離・変動性を同時に考慮
  • Git連携: 実際の変更頻度をデータとして反映
  • 実行可能な提案: 具体的なリファクタリングアクションを提示
  • 複数の出力形式: テキスト/JSON/Web UI/AIフレンドリー
  • CI/CD統合: 品質ゲートとして自動チェック

完璧な設計を目指す必要はありません。「80%の改善で十分」というプラグマティックな姿勢で、少しずつプロジェクトの健全性を高めていきましょう。

# まずは試してみてください
cargo install cargo-coupling
cargo coupling --summary ./src

結合の問題が可視化されるだけでも、設計改善の第一歩になります。

次にあなたが「このモジュール、なんか触りたくないな...」と感じたとき、それはもう漠然とした不安ではありません。強度・距離・変動性という3つの軸で分析でき、具体的な改善アクションに落とし込める、対処可能な課題です。その感覚は、恐れではなく、改善の入り口なのです。

似た概念にA Philosophy of Software DesignのComplexity がある。これも良い考え方なので一読をおすすめします。

speakerdeck.com