LESSON 25分

ストーリー

高橋アーキテクト
コードレビューで”なんか嫌な感じ”がすることがあるだろう?
あなた
はい、でもうまく言語化できなくて…
高橋アーキテクト
それが”コードスメル”だ。マーティン・ファウラーが名付けた概念で、コードに問題がある可能性を示す兆候のこと。バグではないけれど、放置するとバグの温床になる。今日は代表的なコードスメルのカタログを見ていこう

コードスメルとは

コードスメル(Code Smell)は、コードの設計上の問題を示す表面的な兆候です。コンパイルエラーやバグとは違い、プログラムは正しく動作します。しかし、メンテナンス性や拡張性に悪影響を与えます。


代表的なコードスメル

1. 長すぎるメソッド(Long Method)

// スメル:1つのメソッドが100行以上
class ReportGenerator {
  generate(data: SalesData[]): string {
    // データのバリデーション(20行)
    // ...
    // データの集計(30行)
    // ...
    // ヘッダーの生成(15行)
    // ...
    // 本文の生成(25行)
    // ...
    // フッターの生成(10行)
    // ...
    return result;
  }
}

// 改善:意味のある単位にメソッドを分割
class ReportGenerator {
  generate(data: SalesData[]): string {
    const validatedData = this.validate(data);
    const summary = this.aggregate(validatedData);
    const header = this.buildHeader(summary);
    const body = this.buildBody(summary);
    const footer = this.buildFooter(summary);
    return `${header}${body}${footer}`;
  }
}

2. 巨大なクラス(Large Class)

// スメル:1つのクラスが何でもやる(God Class)
class UserManager {
  createUser() { /* ... */ }
  deleteUser() { /* ... */ }
  sendWelcomeEmail() { /* ... */ }
  sendPasswordReset() { /* ... */ }
  generateMonthlyReport() { /* ... */ }
  exportToCsv() { /* ... */ }
  validateCreditCard() { /* ... */ }
  calculateShippingCost() { /* ... */ }
}

責任が多すぎるクラスは、変更理由が多すぎます。

3. 重複コード(Duplicated Code)

// スメル:似たようなコードが複数箇所に
class AdminController {
  getUsers() {
    const token = this.request.headers['authorization'];
    if (!token) throw new Error('Unauthorized');
    const user = jwt.verify(token, SECRET);
    if (!user.isAdmin) throw new Error('Forbidden');
    // ... 実際の処理
  }

  getReports() {
    const token = this.request.headers['authorization'];
    if (!token) throw new Error('Unauthorized');
    const user = jwt.verify(token, SECRET);
    if (!user.isAdmin) throw new Error('Forbidden');
    // ... 実際の処理(認証部分が重複)
  }
}

4. 特性の横恋慕(Feature Envy)

// スメル:他のクラスのデータばかり使うメソッド
class OrderPrinter {
  printDetails(order: Order) {
    console.log(`Customer: ${order.customer.name}`);
    console.log(`Address: ${order.customer.address.city}, ${order.customer.address.street}`);
    console.log(`Total: ${order.items.reduce((s, i) => s + i.price * i.qty, 0)}`);
    console.log(`Tax: ${order.items.reduce((s, i) => s + i.price * i.qty, 0) * order.taxRate}`);
  }
}

// 改善:データを持つクラスにロジックを移動
class Order {
  getTotal(): number {
    return this.items.reduce((sum, item) => sum + item.price * item.qty, 0);
  }

  getTax(): number {
    return this.getTotal() * this.taxRate;
  }

  getFormattedDetails(): string {
    return `Customer: ${this.customer.name}\nTotal: ${this.getTotal()}\nTax: ${this.getTax()}`;
  }
}

5. プリミティブ執着(Primitive Obsession)

// スメル:意味のある概念をプリミティブ型で表現
function createUser(
  name: string,
  email: string,       // メールアドレスの形式チェックはどこで?
  phone: string,       // 電話番号の形式チェックはどこで?
  age: number,         // 負の数は? 200歳は?
  role: string         // "admin" のタイポで "adimin" でも通る?
) { /* ... */ }

// 改善:値オブジェクトを使う
function createUser(
  name: UserName,
  email: Email,        // Email クラスが形式をバリデーション
  phone: PhoneNumber,  // PhoneNumber クラスが形式をバリデーション
  age: Age,            // Age クラスが範囲をバリデーション
  role: UserRole       // enum で安全に
) { /* ... */ }

6. スイッチ文の乱用(Switch Statements)

// スメル:同じ条件分岐が複数箇所に散在
function calculatePay(employee: Employee): number {
  switch (employee.type) {
    case 'fulltime': return employee.salary;
    case 'parttime': return employee.hourlyRate * employee.hours;
    case 'contractor': return employee.dailyRate * employee.days;
  }
}

function getVacationDays(employee: Employee): number {
  switch (employee.type) {
    case 'fulltime': return 20;
    case 'parttime': return 10;
    case 'contractor': return 0;
  }
}

同じ switch が増えるたびに、全箇所を修正する必要があります。


コードスメルの検出方法

方法説明
コードレビューチームメンバーの目で検出
静的解析ツールESLint、SonarQube などが自動検出
メトリクス複雑度や行数の計測
直感「なんか変だな」という感覚を信じる

高橋アーキテクトのアドバイス:

「最初は言語化できなくてもいい。“なんか嫌だな”という感覚を持つこと自体が大切だ。それを少しずつ名前で呼べるようになれば、レビューの質が劇的に変わる」


まとめ

ポイント内容
コードスメル設計上の問題を示す表面的な兆候
長いメソッドメソッドを意味のある単位に分割する
巨大なクラス責任を分離してクラスを分ける
重複コード共通化して DRY 原則を適用する
プリミティブ執着値オブジェクトで意味を表現する

チェックリスト

  • 6つの代表的なコードスメルを説明できる
  • 自分のコードでコードスメルを見つけられる
  • コードスメルの検出方法を理解した

次のステップへ

次は「メトリクスで品質を測ろう」です。コードスメルを直感だけでなく、数値で客観的に測定する方法を学びます。


推定読了時間: 25分