こんにちは!ファインディでプロダクト開発部のVPoEをしている浜田です。
Findy Team+では、コードの複雑さを可視化して技術的負債の解消に役立てられる「コード品質分析」という機能の提供を開始しました。
4つのコード品質に関連する指標を組み合わせて、ファインディ独自のスコアとして可視化しています。実際に活用いただく中で、「スコアが低いとき、限られた工数でどの指標から優先的に改善すればよいか」という、リファクタリングの優先順位に踏み込んだご質問をいただくことが増えてきました。
この記事ではコード品質の可視化が求められる背景から、4つの指標の読み解き方、効率的にリファクタリングを進めるための優先順位、具体的なリファクタリングパターンまでを紹介します。 コード品質分析を使っている方はもちろん、複雑度の指標を使ってリファクタリングを進めたいと考えているすべての方に参考にしていただける内容です。
- なぜ今、コード品質の可視化なのか
- コード品質分析のよくある活用事例
- コード品質分析とは
- 品質スコアが低いとき、どの指標から改善するか
- 具体的なリファクタリングパターン
- 指標についてよくいただく質問
- まとめ
なぜ今、コード品質の可視化なのか
これまで人がコードを書いてきた時代にも、レビュー負荷の軽減や属人化の防止、リファクタリング投資の判断材料にするなど、コード品質を測る目的はありました。 一方で、人がコードを書くスピードには物理的な上限があるため、品質に課題のあるコードが積み上がるペースにも自然と歯止めがかかっていました。
AIがコードを書く時代になると、この前提が変わります。 コードの生産量が爆発的に増える一方で、人がレビューできる量には限界があるため、品質への配慮が不十分なコードがこれまでにないスピードで積み上がる可能性があります。 さらに、既存のコードはAIがコードを生成するときの入力にもなるため、品質の低いコードを放置すると、そこから生成されるコードの品質にも影響が波及していきます。
このような変化から、コード品質の可視化は「あれば便利」なものから、AI時代の開発組織に欠かせない前提条件に変わりつつあると考えています。
ファインディではAI時代の開発組織が見るべきものを「開発資本(Engineering Capital)」として定義しています。 開発資本は、Speed・Quality・Controlの3つの軸で開発組織の状態を捉えるフレームワークです。 このうち、コンスタントに価値を届けられるかを捉えるQualityと、変更を予測し制御できるかを捉えるControlでは、コード品質の指標が観点の1つになっています。 この記事で紹介するコード品質分析は、開発資本のQualityとControlをコードの複雑さという切り口から支える機能です。
コード品質分析のよくある活用事例
コード品質分析を活用したくなる場面は、大きく次の3つのパターンに分けられます。
| 活用事例 | 内容 |
|---|---|
| リファクタリング投資の意思決定・効果測定 | どこに負債が溜まっているか、直した結果どう変わったかを数字で示したい |
| モジュール間やAIと人のコード品質比較 | モジュールごとや、AIが生成したコードと人が書いたコードの品質を横並びで把握して、改善に活かしたい |
| OSS化や外部公開の判断 | 対外的に公開するコードなので、客観的な指標で品質を確認したい |
いずれの活用事例も、根底にあるのは「コードの質を語れるデータが欲しい」という共通の課題です。 この「質を語れるデータ」を提供するのがコード品質分析です。
コード品質分析とは
コード品質分析は、コードの複雑さを可視化し、どこに技術的負債が溜まっているか、どこをリファクタリングすべきかを判断できるFindy Team+の分析機能です。 リポジトリ・ファイル単位で、次の4つの指標を計測しています。
| 指標 | 何を測っているか |
|---|---|
| 循環的複雑度 | プログラム中の「分岐の数」 |
| 認知的複雑度 | 人間がコードを読むときの「頭の負担」 |
| 保守性指数 | 0〜100の総合スコア。大きいほど良好 |
| 最大ネスト深度 | 「ifの中のifの中の…」という階層の深さ |
それぞれの指標はソフトウェア開発で一般的に広く使われているものであり、Findy Team+ではこの4つの指標を組み合わせて独自の品質スコアを算出しています。 「読みづらさ・変更しにくさ」という同じ課題を異なる角度から幅広く捉え、バランスよく評価できるように設計しています。
4つを組み合わせることで、「どこが」「どう悪いのか」を具体的にチームにフィードバックできるようになります。 各指標には「Good」とされる目安の水準があり、どのファイルのどの指標が目安を超えているかをツール上で確認できます。
なお、コード品質分析が測っている「保守のしやすさ」は、システムやソフトウェアの品質を評価する国際規格ISO/IEC 25010が定義する品質特性の1つ「保守性」に対応しています。 「保守性のスコアがどう変化したか」という報告は、感覚値ではなく国際標準の品質特性に基づいた説明になるため、経営層やステークホルダーへの報告にも活用できます。
これらの指標を活用すると、リポジトリごとに技術的負債がどこに溜まっているかを数字で把握し、リファクタリング前後で品質がどう変わったかをグラフで示せるようになります。 モジュールやリポジトリを横並びで比較して組織としての打ち手を決めたり、「開発スピードだけでなく、質も担保している」ことをデータで説明したりと、先ほどの3つの活用事例はいずれもこの機能で実現できます。
品質スコアが低いとき、どの指標から改善するか
コードの品質は「将来の変更のしやすさ」を左右します。 品質が低いコードは、機能追加や不具合修正のたびに時間がかかり、修正時に新しい不具合も生まれやすくなります。
コード品質分析を導入する前は、「最近触っていて読みづらかったから」といった開発者の勘や記憶を頼りに、リファクタリング対象を選ぶことが多いのではないでしょうか。 導入後はファイル単位で指標が可視化されるため、「どのファイルの、どの指標が悪いのか」という数字を起点に着手先を決められるようになります。
とはいえ、4つの指標すべてが悪化しているファイルを前にすると、どこから手を付ければよいか迷ってしまいます。 そんなときは、品質の低いコードを「長くて読みにくいマニュアル文書」にたとえてみると、それぞれの指標の意味を直感的に捉えられます。
| 指標 | 文書にたとえると |
|---|---|
| 最大ネスト深度 | 括弧の中に括弧、さらにその中に括弧…という入れ子の深さ |
| 保守性指数 | 1つの章が長すぎて全体像がつかめない度合い |
| 認知的複雑度 | 読む人の頭にかかる負担の総量 |
| 循環的複雑度 | 「Aの場合は…Bの場合は…」という場合分けの数 |
このたとえを踏まえて、私がおすすめしている改善の順序を紹介します。
まず「入れ子」を浅くする
「もし〜なら、もし〜なら、もし〜なら…」と何重にも条件が重なった箇所は、読む人が頭の中で覚えておくべき前提が積み重なり、理解の妨げになります。 これを「該当しない場合は先に終了する」という書き方に直します。
後述しますが、この改善は作業が小さく、既存の動きを壊すリスクが最も低いのに、読みやすさへの効果が大きいのが特徴です。
次に、大きすぎるファイルを役割のまとまりごとに分ける
品質が低いファイルの多くは、1つのファイルが複数の役割を抱え込んでいます。 文書で言えば「1つの章に無関係な話題が詰め込まれている」状態です。
役割ごとに分けると、探しやすさ・直しやすさが根本的に改善します。 ただし「短くするための機械的な分割」は、かえって話があちこちに飛ぶ読みにくい文書になるのと同じで逆効果なので、あくまで意味のまとまりで分けます。
最後に、残った場合分けを個別に判断する
ここまでの改善を終えると、読み手の負担である認知的複雑度は大部分が自然に解消しています。 最後に残る「場合分けの数」は、業務ルールそのものの複雑さを映していることが多い点に注意が必要です。
実際の業務に10通りの場合分けがあるなら、コードにも10通りの場合分けが必要です。 これを無理に減らすとかえって分かりにくくなるため、「本当に減らせる場合分けか」を見極めながら最後に対応します。
この順序をおすすめする理由は、「安全で小さい改善→構造の整理→慎重な判断が必要な改善」という並びになっていることです。 改善作業そのものが新しい不具合を生むリスクを最小限に抑えながら進められます。
ここまでの「この指標がこう出たらこう判断する」という流れをフローチャートにまとめると、次のようになります。
flowchart TD
A["品質スコアが低いファイルを特定"] --> B{"最大ネスト深度が深い?"}
B -->|はい| C["ガード節で入れ子を浅くする"]
B -->|いいえ| D{"保守性指数が低い?"}
C --> D
D -->|はい| E["ファイルを役割のまとまりで分割する"]
D -->|いいえ| F{"循環的複雑度が高い?"}
E --> F
F -->|はい| G{"業務ルール由来の場合分け?"}
F -->|いいえ| K{"認知的複雑度は下がった?"}
G -->|いいえ| I["場合分けを整理して減らす"]
G -->|はい| J["条件に名前を付けて読みやすくする"]
I --> K
J --> K
K -->|下がった| H["読みやすさの改善を確認して完了"]
K -->|まだ高い| M["残った書き方由来の複雑さを見直す"]
フローの最後に認知的複雑度を確認しているのは、この指標が「どこから直すか」ではなく「どれだけ読みやすくなったか」という成果を映すものさしだからです。 入れ子の解消やファイル分割を進めると認知的複雑度は自然に下がっていくので、リファクタリングの前後で見比べれば、狙いどおり読みやすさが改善したかを数字で確認できます。 まだ高いままなら、書き方由来の複雑さが残っているサインなので、対象のファイルを見直してみてください。
具体的なリファクタリングパターン
改善の順序で挙げた「入れ子を浅くする」「ファイルを役割ごとに分ける」「場合分けを整理する」を、具体的なコード例とともに見ていきます。 言葉だけではイメージが浮かびにくいので、代表的なパターンを順番に紹介します。
ガード節(早期リターン)で入れ子を浅くする
改善順序の1番目に挙げた「該当しない場合は先に終了する」書き方は、ガード節(早期リターン)と呼ばれています。
まずは改善前のコードです。条件を満たす場合をどんどん掘っていく書き方で、ネストが4段になっています。
function applyCoupon(user, order, coupon) { if (user !== null) { if (user.isActive) { if (order.total >= coupon.minAmount) { if (!coupon.isExpired) { // ここでやっと本題(クーポン適用) return order.total - coupon.discount; } else { return order.total; } } else { return order.total; } } else { return order.total; } } else { return order.total; } }
読む人は「ユーザーがいて、かつ有効で、かつ金額を満たしていて、かつ期限内で…」という前提を頭に積み上げたまま本題にたどり着く必要があります。 さらにelseが下に離れて置かれるため、「このelseはどのifと対応しているのか」を目で追う負担も加わります。
これをガード節で書き直すと、ネストは0〜1段になります。
function applyCoupon(user, order, coupon) { // 適用できないケースを先に片付ける if (user === null) return order.total; if (!user.isActive) return order.total; if (order.total < coupon.minAmount) return order.total; if (coupon.isExpired) return order.total; // ここから下は「クーポンが適用できる」と確定した世界 return order.total - coupon.discount; }
ポイントは2つあります。 1つは、前提を覚えておく必要がなくなることです。「returnを通過した=その条件はもうクリア済み」なので、本題のコードを読むときに頭の中は空っぽでよくなります。
もう1つは、動きが何も変わっていないことです。 条件の書き方を裏返して並べ直しただけなので、既存の挙動を壊すリスクが極めて低い改善です。改善順序の最初におすすめした根拠もここにあります。
ループの中ではcontinueが同じ役割
ループの中で条件分岐が積み重なる場合は、continueがガード節と同じ役割を果たします。
// 改善前: ループ内にネストが積み上がる for (const member of members) { if (member.isActive) { if (member.email !== null) { sendNewsletter(member.email); } } } // 改善後: 対象外を先にスキップする for (const member of members) { if (!member.isActive) continue; if (member.email === null) continue; sendNewsletter(member.email); }
「該当しない行は読み飛ばす」という宣言が先頭に並ぶので、ループの本題であるメール送信が一目で分かるようになります。
役割ごとに関数・ファイルを分ける
改善順序の2番目に挙げた「大きすぎるファイルを役割のまとまりごとに分ける」も、まずは関数単位で切り出すと着手しやすくなります。
次の関数は、1つの中で「入力の検証」「金額の計算」「通知の送信」という3つの役割を抱え込んでいます。
// 改善前: 1つの関数が検証・計算・通知を抱えている function checkout(user, order, coupon) { if (user === null) throw new Error("ユーザーが不正です"); if (order.items.length === 0) throw new Error("カートが空です"); let total = order.items.reduce((sum, item) => sum + item.price, 0); if (coupon !== null && !coupon.isExpired) { total -= coupon.discount; } sendEmail(user.email, `ご注文ありがとうございます。合計 ${total} 円です`); return total; }
役割ごとに関数を切り出すと、呼び出し側は「何をしているか」の目次だけを読めば済むようになります。
// 改善後: 役割ごとに関数へ切り出す function validateOrder(user, order) { if (user === null) throw new Error("ユーザーが不正です"); if (order.items.length === 0) throw new Error("カートが空です"); } function calculateTotal(order, coupon) { const subtotal = order.items.reduce((sum, item) => sum + item.price, 0); const isCouponUsable = coupon !== null && !coupon.isExpired; return isCouponUsable ? subtotal - coupon.discount : subtotal; } function checkout(user, order, coupon) { validateOrder(user, order); const total = calculateTotal(order, coupon); sendEmail(user.email, `ご注文ありがとうございます。合計 ${total} 円です`); return total; }
文書でいう「話題ごとに章を分け、目次から必要な章に飛べるようにする」操作に相当します。 1つのファイルが大きくなってきたら、こうしたまとまりを別ファイルに移すことで、ファイルあたりの分量が減り、保守性指数の改善につながります。
単純な場合分けは対応表に逃がす
改善順序の3番目に挙げた「場合分けの数」そのものを減らせるのは、分岐が単純な対応関係になっているケースです。
次のコードは、会員ランクごとに割引率を返すだけの分岐が並んでいます。
// 改善前: ランクの数だけ分岐が増える function getDiscountRate(rank) { if (rank === "bronze") return 0.03; if (rank === "silver") return 0.05; if (rank === "gold") return 0.10; if (rank === "platinum") return 0.15; return 0; }
「入力に対して値を1つ返すだけ」の分岐は、対応表(オブジェクト)に置き換えると分岐そのものがなくなります。
// 改善後: 分岐をデータに置き換える const DISCOUNT_RATES = { bronze: 0.03, silver: 0.05, gold: 0.10, platinum: 0.15, }; function getDiscountRate(rank) { return DISCOUNT_RATES[rank] ?? 0; }
分岐が減るので循環的複雑度が下がり、ランクが増えても表に1行足すだけで済みます。 一方で、分岐ごとに処理の中身が違う場合や、業務ルールとして意味のある場合分けは、無理に表へ押し込めません。その場合は、次のように名前を付けて読みやすくする方向で対応します。
どうしても消えない条件は、名前を付けて外に出す
条件が業務ルールとして本当に必要な場合は、ネストを消すのではなく、条件の塊に名前を付けて1段浅く見せる方法があります。
// 改善前 if (order.total >= coupon.minAmount && !coupon.isExpired && coupon.usedCount < coupon.maxUses) { // クーポンを適用する } // 改善後: 条件の意味に名前を付ける function isCouponApplicable(order, coupon) { return order.total >= coupon.minAmount && !coupon.isExpired && coupon.usedCount < coupon.maxUses; } if (isCouponApplicable(order, coupon)) { // クーポンを適用する }
文書でいう「長い括弧書きを脚注に逃がして、本文は一言で済ませる」操作に相当します。 本文を読む人は「クーポンが適用できるなら」とだけ理解すればよく、詳細が必要になったときだけ中身を読みに行けます。
指標についてよくいただく質問
最後に、コード品質分析の指標についてよくいただく質問と回答を紹介します。
循環的複雑度と認知的複雑度は何が違う?
循環的複雑度は分岐の「数」を機械的に数える指標で、認知的複雑度は読む人の「頭の負担」を測る指標です。 大きな違いは、認知的複雑度はネストが深い場所にある分岐ほど重くカウントされることです。
たとえば、この記事で紹介したガード節への書き換えでは、分岐の数自体はほとんど変わらないため循環的複雑度はあまり下がりませんが、ネストが解消されるため認知的複雑度は大きく下がります。 「循環的複雑度には業務の複雑さ、認知的複雑度には書き方の複雑さが表れやすい」と捉えると、使い分けしやすくなります。
4つの指標はすべて目安(Good)の値まで下げるべき?
目安はあくまで優先順位を決めるためのシグナルであり、すべてのファイルで達成すべきノルマではありません。 特に循環的複雑度は業務ルールの複雑さを反映していることが多く、無理に下げるとかえって読みにくくなる場合があります。
「目安を超えているファイルから順に中身を見て、書き方由来か業務由来かを見極める」という使い方をおすすめしています。
どのファイルからリファクタリングを始めればいい?
スコアが悪い順ではなく、「今後も変更する予定があるファイル」から始めるのがおすすめです。 コードの品質が問題になるのは変更するときなので、スコアが低くても今後ほとんど触らないファイルの優先度は下げて構いません。
逆に、これから機能追加を予定している箇所にスコアの低いファイルがあれば、着手前にリファクタリングを検討する価値があります。
なお、優先度を下げてよいというのは、やらなくてよいという意味ではありません。 冒頭で触れたとおり、既存のコードはAIがコードを生成するときの入力にもなるため、品質の低いファイルを放置すると、人が直接触らなくてもAIが生成するコードの品質に影響する可能性があります。 変更予定のあるファイルから着手しつつ、残ったファイルも計画的に改善を進めていくことが大切です。
まとめ
この記事では、コード品質の可視化が求められる背景から、コード品質分析の4つの指標の読み解き方、品質スコアが低いときの改善の優先順位、具体的なリファクタリングパターンまでを紹介しました。
4つの指標が同時に悪化していても、「ガード節で入れ子を浅くする→ファイルを役割ごとに分ける→残った場合分けを個別に判断する」という順序で進めれば、リスクを抑えながら効率的に品質を改善できます。 まずは一番安全で効果の大きいガード節から始めてみてください。
AIがコードを書く時代、コードの品質を定量的に把握してデータに基づいて意思決定することは、今後ますます重要になると考えています。 Findy Team+のコード品質分析は、その第一歩として、どこから効率的にリファクタリングを進めればよいかをデータで明らかにします。
Findy Team+に興味を持たれた方は、ぜひこちらのページからお問い合わせください。






