LESSON 25分

ストーリー

高橋アーキテクト
“なんとなく悪い”では、改善の優先順位がつけられない
高橋アーキテクト
数値で測れるものは改善できる。今日はコード品質を客観的に測定するメトリクスを学ぼう。感覚を数値に変換する方法だ

循環的複雑度(Cyclomatic Complexity)

循環的複雑度は、コード内の独立した実行パスの数を測定します。条件分岐が増えるほど複雑度が上がります。

計算方法

ifelse ifforwhilecase&&||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 のメソッドでも、本質的に複雑な問題を扱っているなら許容できる場合もある。大切なのは、“なぜその数値なのか” を説明できることだ」

  1. ベースラインを測定する — 現在の状態を知る
  2. ホットスポットを特定する — 最も問題のあるファイルを見つける
  3. 改善の優先順位をつける — 変更頻度が高いファイルから改善
  4. トレンドを追跡する — 改善が進んでいるか確認

まとめ

ポイント内容
循環的複雑度条件分岐の数を測定、10以下が目安
凝集度モジュール内の関連性、高いほど良い
結合度モジュール間の依存性、低いほど良い
活用法絶対値ではなくトレンドと文脈で判断

チェックリスト

  • 循環的複雑度を計算できる
  • 凝集度と結合度の違いを説明できる
  • メトリクスの適切な活用方法を理解した

次のステップへ

次は「リーダブルコードの原則」です。メトリクスだけでは測れない、人間が読みやすいと感じるコードの書き方を学びます。


推定読了時間: 25分