4.1 コードの複雑さの指標
概要と動機
1976年にトーマス・J・マッケイブによって導入されたサイクロマティック複雑度は、あるコードの制御フローを通じた独立した経路の数を数えます。各if文、ループ、分岐が件数に加わります。これは、ネストされた追いにくい制御フローをマッケイブの元の線形の件数よりも重く重みづけする認知的複雑度や、ネストの深さのような親戚とともに、ほぼ50年後の今も最も広く使われるコード複雑さの指標であり続けています。これらの指標は、本物で検証された洞察を共有しています。より多くの独立した経路を持つコードは、完全にテストするのがより難しく、推論するのがより難しく、数十年にわたる実証研究において測定可能により欠陥を含む可能性が高いというものです。
本トピックはその洞察を本物の敬意をもって扱いながら、その限界も同じ真剣さをもって扱います。複雑さの指標はコードの一つの特定の特性を測定し、あるコードベースはどんな複雑さの指標によっても単純でありながら、どんな分岐を数えるアルゴリズムも検出できない形で、設計が悪く、命名が悪く、概念的に首尾一貫していないことがあります。逆に、いくつかの本質的に複雑な問題は、正しく解決するために本物に複雑なコードを必要とし、複雑さのスコアを最小化するプレッシャーをかけられたチームは、スコアは良いが実際にはより理解しにくい、本質的な複雑さを減らすのではなく、より多くのファイルや間接参照の層に広げたコードを生み出すことがあります。
大規模なチームにとって、複雑さの指標はトリアージの道具としてその価値を発揮します。何千ものファイルの中から、より詳しく見る価値が最も高い小さな部分集合を見つける方法であり、コードの品質についての独立した判決としてではありません。どの個人も完全には読めないほど大きなコードベースを維持する企業や政府の組織は、限られたリファクタリングとレビューの努力を最も良い効果をもたらす場所へと向けるために、このトリアージの機能に依存しています。
重要な原則
- 複雑さの指標はテストと欠陥の難しさを予測するが、品質を直接測定するものではない。 それらを判決としてではなく一つの入力として扱ってください。
- 複雑さのスコアは、本物の単純化だけでなく難読化を通じた操作にもさらされている。 複雑さをより多くのファイルに分割することは、実際にコードを理解しやすくすることなくスコアを下げることができます。
- いくつかの複雑さは偶有的ではなく本質的である。 本物に難しい問題は本物に複雑なコードを必要とするかもしれません。目標は複雑さを無差別にすべて排除することではなく、偶有的複雑さを最小化することです。
- 複雑さの指標はトリアージのために使い、個人やチームのスコアカードとしては使わないでください。 それらはどこを見るべきかを指し示すものであり、誰を非難すべきかを指し示すものではありません。
- 傾向と外れ値は、どんな絶対的な閾値よりも重要である。 上昇する傾向や極端な外れ値は、単一のチーム全体の平均よりも行動可能です。
推奨事項
複雑さの指標をレビューとリファクタリングの努力のトリアージに使う
コードベース全体にわたって複雑さの分析を実行し、その結果を使って、より詳しい人間のレビューやリファクタリングへの投資が最も報われる場所を優先順位づけてください。コードベース自体の典型的な範囲をはるかに超えてスコアされる関数やファイルは、最初に見るべき最も価値の高い場所です。このトリアージの使い方、どこを見るべきかを見つけることは、複雑さの指標の最も擁護可能で価値のある応用であり、絶対的な合格不合格のゲートとして使うよりもはるかに優れています。
普遍的な数字ではなく、あなた自身のコードベースに相対的に閾値を設定する
業界の慣例から無批判に借用された絶対的な複雑さの閾値(複雑さスコア10は一般的に引用される経験則です)は、あなたの領域によって緩すぎたり厳しすぎたりすることがあります。パーサーやルールエンジンは、典型的なCRUDサービスよりも正当により高いベースラインの複雑さを持つかもしれません。あなた自身の閾値をあなたのコードベースの実際の分布と照らし合わせて較正し、あなたのチームがそのトレードオフを完全に認識した上で意図的にその厳しい方針を選んでいない限り、閾値違反を自動的なビルド失敗ではなく、より詳しく見るための合図として扱ってください。
本物の単純化なしの分解を通じた操作に注意する
複雑さのスコアが操作される最も一般的な方法は、この特定の指標に適用されたトピック1.2の代替パターンです。一つの本物に複雑な関数を、個別にはよいスコアを得る複数の小さな関数に分割する一方で、全体のシステムは同じくらい理解が難しいままであるか、時にはより難しくなります。なぜなら、ロジックが今やより多くのファイルに散らばり、それらの間により多くの間接参照があるからです。複雑さの指標を、分解が本物にコードを明確にしたか、それとも単に複雑さを指標がもはや見ることができない場所に移しただけかについての定性的なレビューと対にしてください。
反応する前に本質的複雑さを偶有的複雑さから区別する
高い複雑さのスコアを修正すべき問題として扱う前に、根底にある問題が本物にそれだけの数の独立した経路を必要とするのか(たとえば税法の計算ロジックは正当に多くの分岐を持ちます)、それとも複雑さが避けられる原因から来ているのか(平坦化できる深くネストされた条件文、統合できる重複したロジック、あるいは引き直せる不明確な責任の境界)を問うてください。二番目のカテゴリーだけが、この指標があなたを修正へと駆り立てるべき本物の品質の問題です。
一時点の平均だけでなく傾向と外れ値を追跡する
コードベース全体の平均複雑さのスコアがわずかに動くことは、それ自体ではめったに行動可能ではありません。特定のファイルの複雑さが複数の変更にわたって急激に上昇すること、あるいは他の点ではうまく振る舞っているコードベースの中の少数の極端な外れ値の方が、はるかに有用な信号です。時間にわたる傾向と外れ値の裾の両方を追跡し、それらを使って、広範で焦点の定まらない複雑さ削減の取り組みではなく、具体的で的を絞った調査を引き起こしてください。
トレードオフ:長所と短所
| アプローチ | 長所 | 短所 |
|---|---|---|
| 絶対的な普遍の閾値 | 単純で一貫しており、自動化しやすい | 正当な領域の違いを無視する。分解によって操作されうる |
| コードベース相対の閾値 | 実際の文脈によりよく較正されている | より多くのセットアップと定期的な再較正が必要 |
| 自動化されたビルドゲートとしての複雑さ | 人間のレビューのオーバーヘッドなしに一貫性を強制する | 正当に複雑だがよく設計されたコードをブロックするか、難読化された分解を報いることがある |
| 人間のレビューのためのトリアージ信号としての複雑さ | 分解だけでは見逃されるであろう本物の品質の問題を捉える | 完全に自動化されたゲートよりも多くの人間のレビュー時間が必要 |
中心にある緊張関係は自動化と判断です。完全に自動化された複雑さのゲートは強制するのが安く一貫していますが、正当に複雑でよく設計されたコードをブロックすることもあれば、何も本物に単純化することなくスコアを操作する表面的な分解を報いることもあります。この緊張を解消するには、自動化された複雑さの分析をレビューの候補を表面化させるために使い、この複雑さは本質的か偶有的か、このリファクタリングは本物に明確にしたのか単に複雑さを移動させただけなのかという実際の判断は、硬直的な自動化されたゲート単独にではなく、人間のレビュー担当者のためにとっておいてください。
チームで話し合うべき問い
私たちの複雑さの閾値は私たち自身のコードベースの実際の分布に較正されているでしょうか。それとも一般的な業界の慣例から無批判に借用されたものでしょうか。 あなたのコードベースの実際の複雑さの分布を持ち出し、一般的に引用される数字があなたの領域に普遍的に当てはまると想定するのではなく、あなたの現在の閾値がそれと照らし合わせて意味をなすかを確認してください。
私たちは、結果として生じるコードが実際には理解しやすくならないまま、ある関数が複数の小さなものに分割されたのを見たことがあるでしょうか。 これは、本トピックが警告する分解操作のパターンの最も明確な兆候です。主に複雑さのスコアによって動機づけられた最近のリファクタリングを見て、それが本物の理解しやすさを改善したかを正直に評価してください。
私たちのコードベースのどこで複雑さは問題にとって本質的で、どこで偶有的で修正可能でしょうか。 あなたの最も複雑さの高い外れ値をたどり、それらをこの二つのカテゴリーに明示的に分類してください。なぜなら、二番目のカテゴリーだけが本物の行動可能な品質の問題を表すからです。
私たちは複雑さの指標をレビューの努力のトリアージに使っているでしょうか。それとも人間の判断を伴わない厳格な自動化されたゲートとして使っているでしょうか。 あなたの現在の執行アプローチが、本トピックが推奨する本質的対偶有的の区別のための余地を残しているか、それとも文脈にかかわらずすべての違反を同じように扱っているかを話し合ってください。
複雑さのスコアが、たとえ非公式にであっても、個々のエンジニアの仕事の質を判断するために使われたことがあるでしょうか。 これは、活動の指標についてトピック3.4が警告するのと同じ個人評価の罠を、ここではコードの指標に適用したリスクを冒し、同じ操作の反応を招きます。
私たちの最も重要で最も頻繁に変更されるファイルについて、過去1年の私たちの複雑さの傾向はどうなっているでしょうか。 これをトピック4.3のチャーンとホットスポットの分析と組み合わせてください。なぜなら、高度に複雑で頻繁に変更されるファイルは、複雑だがめったに触られないファイルよりもずっと先に注意に値するからです。
業種別の視点
スタートアップ。 複雑さの指標はこの規模では通常より緊急性が低いものです。コードベースのサイズは、非公式な親しみが正式な測定をしばしば代替できるほど小さいものです。早めに採用する価値のある習慣は、チームが非公式に気づくには大きくなりすぎる前に、特定のファイルが静かに管理不能になりつつあるのを捉えるために、時折複雑さのスキャンを実行することです。
中小企業。 ほとんどの現代的な静的解析ツールは、より広く無料か低コストのリント設定の一部として複雑さの指標を報告します。専用のツールに投資するのではなく、その出力を定期的なトリアージの信号として使ってください。最も頻繁に修正されるファイルに最初に注意を向けてください。
企業。 規模における複雑さの指標は、どの個人も手動で調査できないほど大きなコードベースにわたってリファクタリングへの投資を優先順位づけるために、チャーンデータ(トピック4.3)と組み合わせると最も価値があります。正当な複雑さは異なる種類のシステムにわたって大きく異なるため、組織全体で一つの数字を適用するのではなく、サービスや領域ごとに閾値を較正してください。
政府。 長寿命の政府システムは、何年あるいは何十年もの段階的な要件変更を通じてしばしば複雑さを徐々に蓄積し、複雑さの監査は、そうでなければシステムを単に「機能している」から投資する価値がないと見なすかもしれない利害関係者に、近代化やリファクタリングへの投資を正当化するための説得力のある具体的な道具になることができます。
事例
企業。 ある決済処理会社は初めてコードベース全体の複雑さの監査を実行し、コードベースの中央値の10倍以上のサイクロマティック複雑度スコアを持つ単一の取引検証関数を発見しました。調査の結果、この複雑さはほぼ完全に偶有的であることが判明しました。特定の決済プロバイダーのための特殊ケース処理が何年にもわたって段階的に追加され、深くネストされた条件文へと蓄積しており、これはプロバイダー固有のロジックを分離する、よりクリーンな戦略パターンへと再構造化できるものでした。複雑さの監査がコードベースにおける単一の最も価値の高いターゲットとしてそれを特定したために直接優先順位づけられたこのリファクタリングは、その関数の複雑さのスコアを80%以上削減し、より重要なことに、その後の2四半期にわたってその特定のコードパスにおける欠陥率を測定可能に削減しました。
政府。 ある税務当局の何十年も前からある給付計算エンジンは、ほぼすべての関数にわたって複雑さの指標で極めて高いスコアを示し、当初はシステム全体がゼロからの書き直しを必要とするという想定を促しました。本質的複雑さを偶有的複雑さから区別する、関数ごとのより詳しいレビューは、複雑さのほとんどが本物に根底にある法的規則を反映しており、これは本当に法令によって義務づけられたそれだけの数の正当な分岐と特殊ケースを持っていたこと、そして小さな部分集合だけが類似の計算経路にわたる避けられる重複から来ていたことを発見しました。このチームは偶有的複雑さの部分集合だけをリファクタリングの対象とし、高くてリスクの高い完全な書き直しを避けながら、システムの本物に問題のある領域を意味のある形で改善しました。
ビジネスケース:動機、ROI、総所有コスト
複雑さの指標をうまく使うことからの見返りは、的を絞った高価値のリファクタリング投資です。上記の決済会社の例は、複雑さの分析を通じて特定された単一の的を絞った修正が、まさに最もリスクの高いコードパスにおける欠陥を測定可能に削減したことを示しており、広範で的を絞らないリファクタリングの取り組みが必要としたであろうもののごく一部のコストでです。
総所有コストは低いものです。ほとんどの現代的な開発ツールチェーンは、静的解析(トピック4.4)の一部として自動的に複雑さの指標を計算し、本物の投資は、重大な新しいツールのコストではなく、結果を正しく解釈する人間の判断の時間です。本質的複雑さを偶有的複雑さから区別し、分解の操作を捉えることです。
アンチパターンと落とし穴
- 複雑さのスコアを直接の品質判決として扱う。 それは一つの特定の特性を測定するのであり、全体のコード品質ではありません。
- 本物の単純化なしにスコアを操作するために関数を分割する。 本トピックが特に名指しする分解操作のパターンです。
- あなた自身のコードベースに較正せずに普遍的な閾値を適用する。 領域によって緩すぎたり厳しすぎたりする執行を生み出します。
- 複雑さの指標を使ってエンジニアを個別に評価する。 操作を招き、判断ではなくトリアージのために意図された指標を誤って適用します。
- すべての複雑さを等しく修正可能として扱う。 本物に難しい問題からの本質的複雑さは、排除すべき欠陥ではありません。
- 平坦なコードベース全体の平均を優先して傾向と外れ値を無視する。 この指標の家系が提供する最も行動可能な信号を見逃します。
成熟度モデル
- レベル1、開始: 複雑さは測定されていないか、あるいは検証されていない一般的な普遍の閾値を無批判に適用して測定されています。
- レベル2、発展: 複雑さの指標は収集されていますが、めったに行動につながらず、本質的複雑さと偶有的複雑さの間の区別はなされていません。
- レベル3、標準化: 閾値はコードベース自体の分布に較正されており、複雑さの指標が組織全体で一貫してレビューとリファクタリングのトリアージを駆動しています。
- レベル4、管理: 複雑さの傾向と外れ値が能動的に監視され、リファクタリングへの投資を優先順位づけるためにチャーンデータ(トピック4.3)と組み合わされ、分解の操作が能動的に注視されています。
- レベル5、最適化: 組織は、複雑さに基づいて情報を与えられたリファクタリングへの投資に直接たどれる具体的で測定可能な欠陥率の改善を指し示すことができ、複雑さのデータはエンジニアリング投資の決定への日常的で信頼される入力です。
議論のためのアイデア
- 私たちの唯一最も複雑な関数やファイルは何で、その複雑さは本質的でしょうか、それとも偶有的でしょうか。
- 私たちは本物の単純化なしに分解を通じて複雑さのスコアを操作したことがあるでしょうか。
- 私たちの閾値は私たち自身のコードベースに較正されているでしょうか、それとも無批判に借用されたものでしょうか。
- 私たちのコードベースで今、高い複雑さが高いチャーンとどこで重なっているでしょうか。
- 複雑さのデータはこれまでにリファクタリングへの投資決定に情報を与えたことがあるでしょうか。それとも使われないまま放置されているでしょうか。
主な要点
- サイクロマティック複雑度のような複雑さの指標はテストと欠陥の難しさを予測しますが、全体のコード品質を直接測定するものではありません。
- 高いスコアに反応する前に、本質的複雑さ(本物に難しい問題から来るもの)を偶有的複雑さ(より良い設計によって避けられるもの)から区別してください。
- 分解の操作に注意してください。何も本物に単純化することなくスコアを下げるためにコードを分割することです。
- 複雑さの指標は、個人のスコアカードや硬直的な自動化されたゲートとしてではなく、人間のレビューとリファクタリングの努力を方向づけるトリアージのために使ってください。
- 閾値をあなた自身のコードベースの分布に較正し、平坦な平均だけでなく傾向と外れ値を追跡してください。
参考文献とさらなる読書
- McCabe, Thomas J., “A Complexity Measure,” IEEE Transactions on Software Engineering (1976)。
- Code Complete, Steve McConnell 著。
- Working Effectively with Legacy Code, Michael Feathers 著。
- Campbell, G. Ann, “Cognitive Complexity: A New Way of Measuring Understandability” (SonarSource, 2018)。