この記事では、EDGの手続き型Cアーキテクチャがなぜモノリシックなディスパッチャと相互接続されたヘッダーファイルに依存しているのか、そしてなぜそれらをリファクタリングしようとすると実際にはパフォーマンスと保守性を損なうのかを探ります。
EDGフロントエンドのCppDepend Smart Code Cityを見てみましょう:
視覚的パターンがEDGフロントエンドに示すもの
EDGのレンダリングされた都市の概要を見ると、いくつかのマクロアーキテクチャ上の特徴がすぐに見て取れます:
- ドームはコア地区の高結合を強調:ドームを頂いた赤みがかった建物のクラスターに注目してください。CppDependの3D Cityでは、ドームは高結合の関数を具体的に示します。つまり、コードベースの他の多くの部分に大きく依存したり相互作用したりする関数です。EDGでは、式評価、型解決、宣言処理といったコアタスクが、グローバル状態テーブルやAST構造に絶えずアクセスします。この高い相互依存性(エファレント結合)により、主要なコード地区全体にドームが出現します。
- モノリシックなディスパッチ柱 vs 平らな街区:幅広いフットプリントを持つ明確で高いモノリスが、周囲の平らなブロックから際立っています。これらは
process_expr_workのような関数を反映しています。数千行のコードと高い循環的複雑度を持つ大規模な手続き型Cのswitchループです。 - 色のクラスタリング(中央モジュールでの赤/オレンジの支配):ユーティリティモジュールが寒色系の緑/ティールの面を示す一方、コアのC++フロントエンド地区(
cpfe)は暖色が広範に広がっています。標準的なソフトウェアでは、広範な赤は緊急のリファクタリングの必要性を示しますが、産業用Cコンパイラでは、複雑なC++セマンティクスを効率的に処理するために解析・評価パイプライン全体がどれほど緊密に統合されなければならないかを視覚的に証明しています。 - 緑と青の境界線は強いモジュール健全性を示す:主要なモジュールゾーンを囲む境界線が緑と青のまま保たれていることに注目してください。内部の個々の関数は高度に複雑ですが、CppDependの境界線はトップレベルのアーキテクチャの健全性、ルール準拠、クリーンなモジュール境界を反映しています。
モノリシックなコア:process_expr_work
CppDependのSmart Code Cityでは、src/interpret.cの関数process_expr_workがEDGコードベース全体で最大級の構造の一つとして際立っています:
- コード行数(LOC):2,911
- 循環的複雑度(CC):1,229
- エファレント結合(EC):506
標準的な静的解析ツールにとって、これらのメトリクスは直ちに高優先度のリファクタリングアラートを引き起こします。しかし、EDGの手続き型C設計を理解すると、この構造が意図的かつ効果的である理由が明らかになります。
process_expr_workがこれほど長い理由:手続き型Cのディスパッチループ
EDGは歴史的に手続き型Cで開発されたため、クラス継承や仮想関数テーブルといったC++のオブジェクト指向機能を使用していません。
その代わり、AST(抽象構文木)の走査と式評価は、明示的なタグベースのディスパッチに依存しています:
- タグベースの識別子:ASTノードは、式の種類を表すenum識別子でタグ付けされた生のC構造体ポインタです(例:
EXPR_BINARY、EXPR_CAST、EXPR_CALL)。 - 集中化された手続き型switch:
process_expr_workはメインのインタプリタディスパッチループとして機能します。数百種類のASTノードに分岐する、単一の大規模なC switch文を実行します。 - ローカルレジスタ割り当て&コールオーバーヘッドゼロ:式評価のステップを単一の関数スコープ内に保持することで、再帰的なAST走査中のスタックフレームのプッシュや間接関数呼び出しを回避します。また、標準的なCコンパイラがローカルの一時変数間でレジスタ割り当てを最適化することも可能にします。
Clangが同じ動作をどう実装しているか
Clangはまったく同じC++言語仕様の式を解析・評価しますが、オブジェクト指向のC++アプローチを採っています。EDGの手続き型CディスパッチとClangを比較すると、言語パラダイムが基盤となるアーキテクチャ要件を維持しながら静的解析のシグネチャをどう変えるかが浮き彫りになります:
| メトリクス / 特徴 | EDG(process_expr_work) | Clang(ExprConstant.cpp) |
|---|---|---|
| 言語パラダイム | 手続き型C | モダンC++(オブジェクト指向) |
| アーキテクチャパターン | 集中化されたタグ付きswitchディスパッチループ | AST Visitorパターン(ConstStmtVisitor) |
| コード構造 | 単一のモノリシックな関数(2,900行以上) | クラス階層に分散(VisitExpr、VisitCastなど) |
| 静的解析のシグネチャ | 垂直的複雑度:1つの関数に高いCC(1,229) | 水平的複雑度:メソッドごとのCCは低いがクラス数は多い |
| コールスタック&メモリ | 低いスタックオーバーヘッド、最適化されたレジスタ再利用 | クラス階層をまたぐ仮想/オーバーロードされたメソッド呼び出し |
| コンテキストの受け渡し | ローカル関数スコープと状態への直接アクセス | 明示的なコンテキストオブジェクト(EvalInfo&)をメソッドに渡す |
複雑度の行き先
EDGの場合:複雑度は垂直的で局在化しています。process_expr_work内部に蓄積され、単一の翻訳単位で高い循環的複雑度(1,229)を生み出します。
Clangの場合:複雑度は水平的で分散しています。Clangは評価ロジックを複数の評価クラス(IntExprEvaluator、FloatExprEvaluator、LValueExprEvaluator)の個別のVisit*メソッドに分割します。
Clangは3,000行の単一関数を回避していますが、暗黙の変換、演算子オーバーロード、テンプレートのインスタンス化、方言のエッジケースをすべて含むC++式の評価にはまったく同じルールセットの考慮が必要なため、システム全体の複雑度は同一のままです。
EDGの2,900行の手続き型C switchで表現されようと、Clangのクラス階層にまたがる50のvisitorメソッドで表現されようと、基盤となるドメインの複雑度は変わりません。
ファイルレベルの依存関係サイクル:行列(DSM)の読み方
Smart Code CityからCppDependのDependency Structure Matrix(DSM)に切り替えると、別の主要な静的解析シグネチャが明らかになります。コア翻訳単位間の密なファイルレベルの依存関係サイクルです。
DSMグリッドを見ると、src/class_decl.c、src/declarator.c、src/decls.c、src/expr.cといったファイルが顕著な強連結成分(SCC)を形成しています:
- 青いセル:一方向のレイヤー化された依存関係を示します(ファイルAはファイルBに依存するが、その逆はない)。
- 赤いセル:双方向の依存関係(サイクル)を示します。2つのファイルが互いのシンボルや定義に直接的または間接的に依存しています。
- セルの数値:正確なメンバー結合の重みを示します(例:
declarator.cがdecls.hから31個、declarator.hから5個のメンバーを使用)。
class_decl.c && declarator.cの相互依存
従来のエンタープライズC/C++アーキテクチャでは、翻訳単位間の循環依存は重大な設計上の欠陥と見なされます。
サイクルの解剖:実用的な相互再帰 vs 偶発的結合
CppDependのCode Questを使えば、src/class_decl.cとsrc/declarator.c間の双方向サイクルを駆動する正確な関数呼び出しを検査できます。
declarator.cから使用されるclass_decl.cのメソッド、およびその逆をクエリすることで、2つの異なる結合カテゴリが明らかになります。基本的な言語ルールを反映する実用的サイクルと、容易にリファクタリング可能な偶発的サイクルです。
class_decl.cから使用されるdeclarator.cの関数:
declarator.cから使用されるclass_decl.cの関数:
class_decl.c ↔ declarator.c依存サイクルの意図
1. 実用的な相互再帰
(意図的な設計として維持)
declarator()scan_lambda_declarator()abstract_class_diagnostic()
2. 偶発的結合 / リファクタリング対象
(分離の候補)
f_consume_any_stray_microsoft_rparen()in_cli_property_or_event_definition()in_static_cli_property_or_event_definition()
1. 実用的な側面:言語に内在する相互再帰
モダンC++の解析では、クラス定義とデクラレータの解析は本質的に結びついています。Code Questのクエリは、この依存関係を正当化するコア関数を浮かび上がらせます:
A. declarator.c → class_decl.c
abstract_class_diagnostic(...):関数や変数の宣言が抽象クラスをインスタンス化または返そうとしたときにdeclarator.cから呼び出されます。デクラレータパーサーは、純粋仮想関数を評価し診断を発行するために、class_decl.cからクラスレイアウトのメタデータを問い合わせる必要があります。
B. class_decl.c → declarator.c
declarator(...)とscan_lambda_declarator(...):クラス定義がメンバー関数のデクラレータ、メンバーへのポインタ、ネストされたラムダに遭遇するたびにclass_decl.cから呼び出されます。
delayed_scan_of_exception_spec(...):メンバー関数の例外仕様は、関連するクラスコンテキストの解析後にのみ処理できます。
これらのコア呼び出しを過度な抽象化で排除しようとすると、人為的なラッパー層と実行時のパフォーマンス低下が生じます。コンパイラ工学において、この相互再帰は実用的でドメイン駆動の設計です。
2. リファクタリングの機会:偶発的なユーティリティ結合
しかしCode Questは、コアのC++解析文法とはほとんど関係のない、偶発的なアーキテクチャ結合を表すサイクル内の関数も分離します:
class_decl.cで返された6つのメソッドを見ると:
- 方言/パーサーヘルパー:
f_consume_any_stray_microsoft_rparen() - CLI/拡張ヘルパー:
in_cli_property_or_event_definition()とin_static_cli_property_or_event_definition()
これらが不要なサイクルを生む理由
f_consume_any_stray_microsoft_rparen()は、MSVC固有の構文の癖を処理するパーサー回復ユーティリティです。これをclass_decl.c内に配置すると、declarator.cは単に1つのトークンを消費するためだけに、クラス宣言ユニット全体に依存することになります!
偶発的サイクルを除去する方法
- ユーティリティヘルパーの抽出:方言回復ルーチン(
f_consume_...)とCLI状態チェックを専用のユーティリティモジュール(例:src/parser_utils.cやsrc/lex_helpers.c)に移動すれば、人為的なリンクが断ち切れます。 - 結果:6メソッドの結合フットプリントが縮小し、必要な高性能C++文法ディスパッチループを維持したまま偶発的サイクルが解消されます。
アーキテクトへの重要な示唆
静的解析ツールの警告を「すべてのサイクルを修正する」という一律のルールで扱ってはいけません。
CppDependやCode Questのようなツールを使えば、チームはC++パーサーエンジンに属するドメイン必須のサイクル(declarator() ↔ abstract_class_diagnostic()など)と、クリーンにリファクタリングして除去できる偶発的なユーティリティ配置(MSVCトークン回復ヘルパーなど)を区別できます。
C++はインラインクラス定義、メンバー関数、ネストされたクラス、後置戻り値型を許可するため、クラス定義の解析とデクラレータの解析は根本的に相互再帰的です。
Cヘッダーの仕組みがサイクルを悪化させる理由
EDGは手続き型Cで構築されているため、共有ヘッダー定義(class_decl.h、declarator.h、decls.h)に依存しています。
- 共有C型:
a_type_ptr、a_decl_ptr、a_declaratorのようなデータ構造は、両方の翻訳単位で可視でなければなりません。 - 相互インクルード&前方宣言:クリーンなC++ではインターフェースやPimplパターンでヘッダーを分離できますが、Cのコードベースはヘッダーの相互インクルードと生の構造体ポインタの前方宣言に依存します。DSMでは、これが
class_decl.c、declarator.c、decls.cにまたがる密な赤いクラスターとして現れます。
行列分析への重要な示唆
標準的なソフトウェアアーキテクチャでは、DSMの赤い四角は「インターフェース抽象化で分割せよ」を意味します。
産業用コンパイラフロントエンドでは、class_decl、declarator、decls、expr間の赤い行列クラスターは、C++の文法ルールが純粋な有向非巡回グラフ(DAG)にきれいにレイヤー化できないという現実を反映しているにすぎません。これらのサイクルは偶発的なアーキテクチャの劣化ではなく、C++言語仕様自体に組み込まれた相互再帰を映し出しています。
品質ダッシュボード:23,000件以上の低重要度問題の解読
EDGのCppDepend高レベル品質ダッシュボードを確認すると、印象的な統計的パラドックスが浮上します:
EDG品質ダッシュボードの概要
| メトリクス / カテゴリ | 値 / 詳細 |
|---|---|
| 総コード行数(LOC) | 372,391 |
| 総問題数 | 23,985 |
| 低重要度の問題 | 22,620(全体の約94%) |
| 高重要度の問題 | 1,344 |
| 違反されたクリティカルルール | 0 |
| 品質ゲートステータス | 5/5 合格 |
一見すると、23,985件の総問題数は警戒すべきものに見えるかもしれません。しかし内訳を見れば、EDGがなぜ総合評価Bを達成し、FailやWarnステータスを一度も発生させずに5つの品質ゲートすべてをクリーンに通過しているのかが分かります。
1. 重要度の内訳:ノイズ vs クリティカルリスク
ダッシュボードは問題を明確な重要度レベルに分類します:
- クリティカルな問題:0(深刻なメモリリーク、未定義動作、システム破壊的な欠陥なし)。
- 高重要度の問題:1,344(主に
process_expr_workのような高循環的複雑度の関数に集中)。 - 中重要度の問題:21
- 低重要度の問題:22,620(報告された全問題の約94%)
総問題数の大部分は、MISRA C/C++静的解析チェックのような低重要度のコードスタイルガイドラインに由来します。
2. MISRA C効果:フォーマットルールはLOCに比例して増大
EDGは手続き型Cで書かれているため、MISRA Cのような静的解析ルールは372,391行すべてに厳格な構文ガイドラインを強制します。典型的な例:
「if(condition)構造には複合文(中括弧 {})が続かなければならない。」
古典的な手続き型Cでは、明示的な中括弧のない単一行のif文が一般的です:
// インスタンスごとに低重要度のMISRA Cスタイル違反を引き起こす
if (expr == NULL) return;
// MISRA C準拠の形式
if (expr == NULL) {
return;
}
370,000行以上のコードベースがオプションの中括弧を省略したり、数千のswitchケースでマクロスタイルのパターンを使用したりすると、CppDependはすべての出現箇所を問題としてフラグを立てます。
これらのルールは数万件の個別警告を生成しますが、インスタンスごとの技術的負債への影響は最小限です。これが、Code Implementationが33,280件の問題を示す一方、Code Safetyが0件を示す理由です。
3. 高いコメント密度は強固なドキュメントを反映
ダッシュボードのもう一つの際立ったメトリクスはコメント率です:
- コメント率:48.44%
- コメント行数:349,808
EDGコードベース全体のほぼ50%がコメントで構成されています。実行可能な手続き型Cコードの1行ごとに、C++標準準拠の癖、コンパイラフラグ、言語方言のエッジケースを説明するほぼ1行分のドキュメントがあります。
この卓越したコメント密度が、AST走査ディスパッチャの構造的複雑さにもかかわらず、このコードベースがそのドメインにおいて高い保守性を維持している理由を説明しています。
重要な示唆
品質ダッシュボードは、重要度分類なしでは生の問題数が誤解を招く可能性がある理由を示しています。
EDGの22,620件の低重要度の問題は、表面的なスタイルとフォーマットの違反(MISRAの中括弧準拠など)です。エンジンがクリティカルルール違反ゼロと48%のコメント密度を維持しているため、産業グレードのC++解析を支えながらCppDependのトップレベル品質ゲートを満たしています。
結論:静的メトリクスとドメインアーキテクチャのバランス
EDG C/C++フロントエンドのような成熟した産業グレードのコードベースをCppDependで分析することは、ソフトウェアアーキテクチャの強力な教訓を提供します。静的解析メトリクスは診断指標であって、絶対的な教条ではありません。
EDG分析からの教訓
| 重要な示唆 | アーキテクチャ上の洞察 |
|---|---|
| 1. メトリクス教条よりコンテキスト | CC 1,229の2,900行の関数が常に技術的負債とは限りません。CベースのASTインタプリタでは、コールスタックの割り当てとレジスタオーバーヘッドを防ぎます。 |
| 2. サイクルタイプの区別 | 文法解析の相互再帰(declarator ↔ class_decl)は実用的ですが、迷い込んだMSVCトークンヘルパーは対処可能な技術的負債です。 |
| 3. マクロ vs ミクロの健全性 | 局所的な関数複雑度が高いにもかかわらず、緑の境界線と48%のコメント密度は規律あるシステムレベルのエンジニアリングを証明しています。 |
重要なエンジニアリングの示唆
- 高い複雑度は意図的な設計でありうる:
process_expr_workのような関数が天文学的な循環的複雑度スコア(1,229)に達するのは、C++ AST式評価を高スループットの手続き型Cディスパッチループに集中させているからです。すべての分岐を個別の関数に分割すれば、パラメータドリリング、スタックフレームオーバーヘッド、キャッシュミスが増加し、C++言語仕様の固有の複雑さは低減されません。 - すべての依存関係サイクルが同等ではない:DSMの可視化とCode Questクエリを通じて、ファイルレベルの依存関係サイクルはしばしば二重の性質を持つことが分かりました:
- ドメイン駆動サイクル:
class_decl.cとdeclarator.c間の相互再帰は、C++文法の再帰的ルールを直接反映しています(クラス定義はデクラレータを含み、デクラレータはクラスコンテキストを必要とする)。 - 偶発的サイクル:トークンパーサーヘルパー(
f_consume_any_stray_microsoft_rparen()など)をコアクラスモジュール内に配置すると、クリーンにリファクタリング可能な不要なファイル間結合が生まれます。
- ドメイン駆動サイクル:
- 重要度分類はメトリクスパニックを防ぐ:EDGの品質ダッシュボードは約24,000件の総問題数を記録しています。しかし、違反されたクリティカルルール0件、5/5の品質ゲート合格、厳格なスタイルチェック(MISRA C中括弧準拠など)に由来する約94%の低重要度警告により、プロジェクトは堅実な評価Bを達成しています。48.44%のコメント密度と相まって、このコードベースは生の構造的規模にもかかわらず、例外的な保守衛生を示しています。
ソフトウェアアーキテクトへの最後の言葉
CppDependのような静的解析ツールは、構造的ヒートマップ、アーキテクチャの境界、隠れた結合を暴く上で非常に価値があります。しかし、複雑な工学ドメインにおける静的解析の目標は、あらゆる犠牲を払ってすべてのメトリクスを緑にすることではありません。
コンパイラフロントエンド、ゲームエンジン、リアルタイムOSのいずれを構築する場合でも、自動化された可視化とドメイン制約の深い理解を組み合わせることで、アーキテクトは真の進行中の技術的負債と、実用的で高性能な設計上の決定を区別できます。
