ストーリー
コードスメルとは
コードスメル(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分