ストーリー
リファクタリングとは
リファクタリング(Refactoring): 外部から見た振る舞いを変えずに、コードの内部構造を改善する作業。
重要なポイント:
- 振る舞いは変えない — テストが通り続けることで保証する
- 小さなステップで行う — 一度に大きく変えない
- 頻繁にテストを実行する — 各ステップの後にテストを回す
Extract Method(メソッドの抽出)
最も頻繁に使うリファクタリング技法です。長いメソッドから意味のあるまとまりを新しいメソッドに抽出します。
Before
class InvoiceGenerator {
generate(order: Order): string {
// 小計の計算
let subtotal = 0;
for (const item of order.items) {
subtotal += item.price * item.quantity;
}
// 割引の計算
let discount = 0;
if (order.customer.membershipLevel === 'gold') {
discount = subtotal * 0.1;
} else if (order.customer.membershipLevel === 'silver') {
discount = subtotal * 0.05;
}
// 税金の計算
const taxableAmount = subtotal - discount;
const tax = taxableAmount * 0.1;
// 請求書のフォーマット
let invoice = `Invoice for ${order.customer.name}\n`;
invoice += `---\n`;
for (const item of order.items) {
invoice += `${item.name}: ${item.price} x ${item.quantity}\n`;
}
invoice += `---\n`;
invoice += `Subtotal: ${subtotal}\n`;
invoice += `Discount: -${discount}\n`;
invoice += `Tax: ${tax}\n`;
invoice += `Total: ${taxableAmount + tax}\n`;
return invoice;
}
}
After
class InvoiceGenerator {
generate(order: Order): string {
const subtotal = this.calculateSubtotal(order.items);
const discount = this.calculateDiscount(subtotal, order.customer.membershipLevel);
const tax = this.calculateTax(subtotal - discount);
const total = subtotal - discount + tax;
return this.formatInvoice(order, subtotal, discount, tax, total);
}
private calculateSubtotal(items: OrderItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
private calculateDiscount(subtotal: number, membershipLevel: string): number {
const rates: Record<string, number> = { gold: 0.1, silver: 0.05 };
return subtotal * (rates[membershipLevel] ?? 0);
}
private calculateTax(taxableAmount: number): number {
return taxableAmount * 0.1;
}
private formatInvoice(
order: Order, subtotal: number, discount: number, tax: number, total: number
): string {
const lines = [
`Invoice for ${order.customer.name}`,
'---',
...order.items.map(item => `${item.name}: ${item.price} x ${item.quantity}`),
'---',
`Subtotal: ${subtotal}`,
`Discount: -${discount}`,
`Tax: ${tax}`,
`Total: ${total}`,
];
return lines.join('\n');
}
}
Rename(名前の変更)
コードの意図を正確に伝える名前に変更します。
// Before
function calc(d: any[]): number {
let r = 0;
for (const x of d) {
r += x.p * x.q;
}
return r;
}
// After
function calculateOrderTotal(orderItems: OrderItem[]): number {
let total = 0;
for (const item of orderItems) {
total += item.price * item.quantity;
}
return total;
}
Rename のコツ
| 対象 | ルール | 例 |
|---|---|---|
| 変数 | 中身を表す名詞 | d → orderItems |
| 関数 | 動詞 + 目的語 | calc → calculateOrderTotal |
| ブーリアン | is/has/can/should | flag → isActive |
| 定数 | UPPER_SNAKE_CASE | x → TAX_RATE |
Move Method / Move Field(メソッド/フィールドの移動)
メソッドやフィールドが、本来あるべきクラスに存在しない場合に移動します。
Before
// 特性の横恋慕:OrderPrinter が Order のデータを直接操作
class OrderPrinter {
printSummary(order: Order): string {
const total = order.items.reduce((s, i) => s + i.price * i.quantity, 0);
const tax = total * order.taxRate;
return `Order #${order.id}: ${total + tax}`;
}
}
class Order {
id: string;
items: OrderItem[];
taxRate: number;
}
After
// 計算ロジックをデータを持つクラスに移動
class Order {
id: string;
items: OrderItem[];
taxRate: number;
getSubtotal(): number {
return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
getTax(): number {
return this.getSubtotal() * this.taxRate;
}
getTotal(): number {
return this.getSubtotal() + this.getTax();
}
}
class OrderPrinter {
printSummary(order: Order): string {
return `Order #${order.id}: ${order.getTotal()}`;
}
}
Inline Method / Inline Variable(メソッド/変数のインライン化)
Extract の逆。メソッドや変数が単純すぎて、そのまま展開した方がわかりやすい場合に使います。
// Before:変数が不必要に多い
function isEligible(user: User): boolean {
const age = user.getAge();
const isAdult = age >= 18;
const hasAccount = user.getAccountStatus() === 'active';
const isNotBanned = !user.isBanned();
const result = isAdult && hasAccount && isNotBanned;
return result;
}
// After:シンプルに
function isEligible(user: User): boolean {
return user.getAge() >= 18
&& user.getAccountStatus() === 'active'
&& !user.isBanned();
}
リファクタリングの安全な進め方
高橋アーキテクトのアドバイス:
「リファクタリングの鉄則は”小さく、頻繁に”。1つの変更をしたらテストを実行する。テストが通ったら次の変更。テストが落ちたら、直前の変更を戻して原因を調べる。この繰り返しだ」
| ステップ | 内容 |
|---|---|
| 1. テスト確認 | 既存のテストがすべて通ることを確認 |
| 2. 小さな変更 | 1つのリファクタリングテクニックを適用 |
| 3. テスト実行 | すべてのテストが通ることを確認 |
| 4. コミット | 動作する状態でコミット |
| 5. 繰り返し | 次のリファクタリングへ |
まとめ
| ポイント | 内容 |
|---|---|
| Extract Method | 長いメソッドから意味のある単位を抽出 |
| Rename | 意図を正確に伝える名前に変更 |
| Move Method | メソッドをデータを持つクラスに移動 |
| Inline | 不要な中間変数やメソッドを除去 |
| 安全性 | 小さなステップ + テスト実行の繰り返し |
チェックリスト
- Extract Method を使って長いメソッドを分割できる
- Rename で意図の伝わる名前に変更できる
- リファクタリングの安全な進め方を理解した
次のステップへ
次は「条件分岐の整理」です。複雑な if/else の連鎖をポリモーフィズムで置き換える方法を学びます。
推定読了時間: 30分