Martin Fowler 著『Refactoring』(第 2 版) に基づく、コードスメルの包括的なリファレンスである。コードスメルはより深い問題の症状であり、コード設計に何か問題がある可能性を示す指標となる。

"A code smell is a surface indication that usually corresponds to a deeper problem in the system." — Martin Fowler


Bloaters(肥満児)

効果的に扱うには大きくなりすぎたものを表すコードスメル群である。

Long Method(長すぎる関数)

兆候:

なぜ悪いか:

リファクタリング:

例(Before):

function processOrder(order) {
  // 注文の検証 (20 行)
  if (!order.items) throw new Error('No items');
  if (order.items.length === 0) throw new Error('Empty order');
  // ... さらに検証

  // 合計金額の計算 (30 行)
  let subtotal = 0;
  for (const item of order.items) {
    subtotal += item.price * item.quantity;
  }
  // ... 税、送料、割引

  // 通知の送信 (20 行)
  // ... メール送信ロジック
}

例(After):

function processOrder(order) {
  validateOrder(order);
  const totals = calculateOrderTotals(order);
  sendOrderNotifications(order, totals);
  return { order, totals };
}

Large Class(巨大なクラス)

兆候:

なぜ悪いか:

リファクタリング:

検出基準:

コード行数 > 300
メソッド数 > 15
フィールド数 > 10

Primitive Obsession(プリミティブ型への執着)

兆候:

なぜ悪いか:

リファクタリング:

例(Before):

const user = {
  email: 'john@example.com',     // 単なる文字列
  phone: '1234567890',           // 単なる文字列
  status: 'active',              // マジックストリング
  balance: 10050                 // セント単位の整数
};

例(After):

const user = {
  email: new Email('john@example.com'),
  phone: new PhoneNumber('1234567890'),
  status: UserStatus.ACTIVE,
  balance: Money.cents(10050)
};

Long Parameter List(長すぎるパラメータリスト)

兆候:

なぜ悪いか:

リファクタリング:

例(Before):

function createUser(firstName, lastName, email, phone,
                    street, city, state, zip,
                    isAdmin, isActive, createdBy) {
  // ...
}

例(After):

function createUser(personalInfo, address, options) {
  // personalInfo: { firstName, lastName, email, phone }
  // address: { street, city, state, zip }
  // options: { isAdmin, isActive, createdBy }
}

Data Clumps(データの群れ)

兆候:

なぜ悪いか:

リファクタリング:

例:

// データの群れ: (x, y, z) 座標
function movePoint(x, y, z, dx, dy, dz) { }
function scalePoint(x, y, z, factor) { }
function distanceBetween(x1, y1, z1, x2, y2, z2) { }

// Point3D クラスを抽出
class Point3D {
  constructor(x, y, z) { }
  move(delta) { }
  scale(factor) { }
  distanceTo(other) { }
}

Object-Orientation Abusers(オブジェクト指向の濫用者)

オブジェクト指向の原則を不完全または誤って使っていることを示すスメル群である。

Switch Statements(スイッチ文)

兆候:

なぜ悪いか:

リファクタリング:

例(Before):

function calculatePay(employee) {
  switch (employee.type) {
    case 'hourly':
      return employee.hours * employee.rate;
    case 'salaried':
      return employee.salary / 12;
    case 'commissioned':
      return employee.sales * employee.commission;
  }
}

例(After):

class HourlyEmployee {
  calculatePay() {
    return this.hours * this.rate;
  }
}

class SalariedEmployee {
  calculatePay() {
    return this.salary / 12;
  }
}

Temporary Field(一時的フィールド)

兆候:

なぜ悪いか:

リファクタリング:


Refused Bequest(相続拒否)

兆候:

なぜ悪いか:

リファクタリング:


Alternative Classes with Different Interfaces(異なるインターフェイスの代替クラス)

兆候:

なぜ悪いか:

リファクタリング:


Change Preventers(変更の妨げ)

変更を困難にするスメル群 - ある変更のために他の多くを変える必要が生じる。

Divergent Change(変更の発散)

兆候:

なぜ悪いか:

リファクタリング:

例: User クラスが次の理由で変更される:

→ 抽出: AuthServiceProfileServiceBillingServiceNotificationService


Shotgun Surgery(散弾銃手術)

兆候:

なぜ悪いか:

リファクタリング:

検出基準: 1 フィールドの追加で 5 ファイル以上の変更が必要かを確認する。


Parallel Inheritance Hierarchies(並行継承階層)

兆候:

なぜ悪いか:

リファクタリング:


Dispensables(不要物)

不要であり削除すべきものを表すスメル群である。

Comments(過剰なコメント)

兆候:

なぜ悪いか:

リファクタリング:

良いコメントと悪いコメント:

// 悪い: 「何」を説明している
// users をループしてアクティブかどうか確認
for (const user of users) {
  if (user.status === 'active') { }
}

// 良い: 「なぜ」を説明している
// アクティブなユーザーのみ。非アクティブはクリーンアップジョブで処理される
const activeUsers = users.filter(u => u.isActive);

Duplicate Code(重複したコード)

兆候:

なぜ悪いか:

リファクタリング:

検出ルール: 3 回以上重複するコードは抽出すべきである。


Lazy Class(怠け者のクラス)

兆候:

なぜ悪いか:

リファクタリング:


Dead Code(デッドコード)

兆候:

なぜ悪いか:

リファクタリング:

検出基準:

# 未使用のエクスポートを探す
# 参照されない関数を探す
# IDE の「未使用」警告を確認

Speculative Generality(投機的一般化)

兆候:

なぜ悪いか:

リファクタリング:


Couplers(結合の問題)

クラス間の過剰な結合を表すスメル群である。

Feature Envy(機能の横恋慕)

兆候:

なぜ悪いか:

リファクタリング:

例(Before):

class Order {
  getDiscountedPrice(customer) {
    // customer のデータを多く使っている
    if (customer.loyaltyYears > 5) {
      return this.price * customer.discountRate;
    }
    return this.price;
  }
}

例(After):

class Customer {
  getDiscountedPriceFor(price) {
    if (this.loyaltyYears > 5) {
      return price * this.discountRate;
    }
    return price;
  }
}

Inappropriate Intimacy(不適切な親密さ)

兆候:

なぜ悪いか:

リファクタリング:


Message Chains(メッセージの連鎖)

兆候:

なぜ悪いか:

リファクタリング:

例:

// 悪い: メッセージの連鎖
const managerName = employee.getDepartment().getManager().getName();

// 良い: 委譲を隠す
const managerName = employee.getManagerName();

Middle Man(仲介人)

兆候:

なぜ悪いか:

リファクタリング:


スメル深刻度ガイド

深刻度 説明 対応
Critical 開発をブロックし、バグの原因となる 直ちに修正
High 保守負担が大きい 当該スプリント中に修正
Medium 目に付くが許容範囲 近い将来に計画
Low 軽微な不便 機会があれば修正

簡易検出チェックリスト

コードをスキャンする際に使うチェックリストである。


参考文献