ストーリー
循環的複雑度(Cyclomatic Complexity)
循環的複雑度は、コード内の独立した実行パスの数を測定します。条件分岐が増えるほど複雑度が上がります。
計算方法
if、else if、for、while、case、&&、||、catch のたびに +1 します(基本値は 1)。
// 複雑度: 1(分岐なし)
function add(a: number, b: number): number {
return a + b;
}
// 複雑度: 4(if + else if + else if)
function getGrade(score: number): string {
if (score >= 90) { // +1
return 'A';
} else if (score >= 70) { // +1
return 'B';
} else if (score >= 50) { // +1
return 'C';
} else {
return 'F';
}
}
// 複雑度: 7(ネストした条件と論理演算子)
function validateOrder(order: Order): boolean {
if (!order) return false; // +1
if (!order.items || order.items.length === 0) { // +1, +1 (||)
return false;
}
for (const item of order.items) { // +1
if (item.quantity <= 0) return false; // +1
if (item.price < 0 || item.price > 999999) { // +1, +1 (||)
return false;
}
}
return true;
}
複雑度の目安
| 複雑度 | 評価 | 対応 |
|---|---|---|
| 1-5 | 良好 | そのまま |
| 6-10 | やや複雑 | リファクタリングを検討 |
| 11-20 | 複雑 | 分割すべき |
| 21以上 | 非常に複雑 | 即座にリファクタリング |
凝集度(Cohesion)
凝集度は、モジュール内の要素がどれだけ関連し合っているかを示します。高い凝集度が望ましいです。
// 低い凝集度:関連のない機能が1つのクラスに
class UserUtils {
formatName(first: string, last: string): string {
return `${last} ${first}`;
}
calculateTax(amount: number, rate: number): number {
return amount * rate;
}
sendEmail(to: string, subject: string): void {
// メール送信ロジック
}
generateRandomId(): string {
return Math.random().toString(36).slice(2);
}
}
// 高い凝集度:関連する機能がまとまっている
class UserNameFormatter {
formatFull(first: string, last: string): string {
return `${last} ${first}`;
}
formatInitials(first: string, last: string): string {
return `${last[0]}.${first[0]}.`;
}
formatFormal(first: string, last: string, title: string): string {
return `${title} ${last} ${first}`;
}
}
凝集度の種類(低い順)
| 種類 | 説明 | 望ましさ |
|---|---|---|
| 偶然的凝集 | たまたま同じモジュールにある | 最悪 |
| 論理的凝集 | 似た処理をまとめただけ | 悪い |
| 時間的凝集 | 同じタイミングで実行される | 普通 |
| 機能的凝集 | 1つの明確な目的のために連携 | 最良 |
結合度(Coupling)
結合度は、モジュール間の依存関係の強さを示します。低い結合度が望ましいです。
// 高い結合度:直接的に他のクラスの内部を知っている
class OrderService {
createOrder(userId: string) {
const db = new MySQLDatabase();
const user = db.query(`SELECT * FROM users WHERE id = '${userId}'`);
const mailer = new SmtpMailer('smtp.example.com', 587, 'user', 'pass');
mailer.send(user.email, 'Order Confirmed', '...');
}
}
// 低い結合度:インターフェースを通じて依存
class OrderService {
constructor(
private userRepository: UserRepository, // インターフェース
private notifier: Notifier // インターフェース
) {}
createOrder(userId: string) {
const user = this.userRepository.findById(userId);
this.notifier.notify(user, 'Order Confirmed');
}
}
結合度の種類(高い順)
| 種類 | 説明 | 望ましさ |
|---|---|---|
| 内容結合 | 他モジュールの内部を直接操作 | 最悪 |
| 共通結合 | グローバル変数を共有 | 悪い |
| スタンプ結合 | 不要なデータも含むオブジェクトを渡す | 普通 |
| データ結合 | 必要なデータだけを渡す | 良い |
| メッセージ結合 | メソッド呼び出しのみ | 最良 |
その他の重要なメトリクス
| メトリクス | 説明 | 目安 |
|---|---|---|
| コード行数(LOC) | ファイルやメソッドの行数 | メソッド: 20行以内推奨 |
| ネストの深さ | インデントの最大レベル | 3レベル以内推奨 |
| メソッド数/クラス | クラスが持つメソッドの数 | 10-15以内推奨 |
| パラメータ数 | 関数の引数の数 | 3-4以内推奨 |
| 依存数 | import の数 | 多すぎたら責任過多の兆候 |
メトリクスの活用方法
高橋アーキテクトのアドバイス:
「メトリクスは目安であって、絶対的なルールではない。複雑度が 12 のメソッドでも、本質的に複雑な問題を扱っているなら許容できる場合もある。大切なのは、“なぜその数値なのか” を説明できることだ」
- ベースラインを測定する — 現在の状態を知る
- ホットスポットを特定する — 最も問題のあるファイルを見つける
- 改善の優先順位をつける — 変更頻度が高いファイルから改善
- トレンドを追跡する — 改善が進んでいるか確認
まとめ
| ポイント | 内容 |
|---|---|
| 循環的複雑度 | 条件分岐の数を測定、10以下が目安 |
| 凝集度 | モジュール内の関連性、高いほど良い |
| 結合度 | モジュール間の依存性、低いほど良い |
| 活用法 | 絶対値ではなくトレンドと文脈で判断 |
チェックリスト
- 循環的複雑度を計算できる
- 凝集度と結合度の違いを説明できる
- メトリクスの適切な活用方法を理解した
次のステップへ
次は「リーダブルコードの原則」です。メトリクスだけでは測れない、人間が読みやすいと感じるコードの書き方を学びます。
推定読了時間: 25分