code-refactoring について
コードの動作を保ちながら構造を改善したい開発者向けのリファクタリングスキルです。一般的なコードの臭いと手法を整理し、テストを備えた小さな変更と、その都度の確認を勧めます。
主な機能
- 01リファクタリングの実施時期に関する指針
- 02一般的なコードの臭いの例
- 03コード構造を改善する手法
- 04テストを前提とした安全確認項目
- 05小さな変更と反復的なテスト実行
活用シーン
- 01長いメソッドの分割
- 02ネストした条件分岐の簡素化
- 03プリミティブ値から値オブジェクトへの置換
- 04コードレビューでの安全なリファクタリング
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ moizibnyousaf/ ai-agent-skills --skill code-refactoringインストール後、Claude Code で/code-refactoringを入力して読み込まれたか確認します。
詳細
コードのリファクタリング
リファクタリングの原則
リファクタリングするタイミング
- 新機能を追加する前(変更しやすくしてから、簡単な変更を行います)
- テストが通った後(レッド・グリーン・リファクタリング)
- コードの不吉な臭いに気づいたとき
- コードレビューでフィードバックを受けたとき
リファクタリングしないタイミング
- 対象のコードをカバーするテストがないとき
- セーフティネットがない状態で、期限が厳しいとき
- 近いうちに置き換える予定のコード
- コードの動作を理解していないとき
よくあるコードの不吉な臭い
長すぎるメソッド
// BEFORE: Method doing too much
function processOrder(order: Order) {
// 100 lines of validation, calculation, notification, logging...
}
// AFTER: Extract into focused methods
function processOrder(order: Order) {
validateOrder(order);
const total = calculateTotal(order);
saveOrder(order, total);
notifyCustomer(order);
}
深くネストした条件分岐
// BEFORE: Arrow code
function getDiscount(user: User, order: Order) {
if (user) {
if (user.isPremium) {
if (order.total > 100) {
if (order.items.length > 5) {
return 0.2;
}
}
}
}
return 0;
}
// AFTER: Early returns (guard clauses)
function getDiscount(user: User, order: Order) {
if (!user) return 0;
if (!user.isPremium) return 0;
if (order.total <= 100) return 0;
if (order.items.length <= 5) return 0;
return 0.2;
}
プリミティブ型への執着
// BEFORE: Primitives everywhere
function createUser(name: string, email: string, phone: string) {
if (!email.includes('@')) throw new Error('Invalid email');
// more validation...
}
// AFTER: Value objects
class Email {
constructor(private value: string) {
if (!value.includes('@')) throw new Error('Invalid email');
}
toString() { return this.value; }
}
function createUser(name: string, email: Email, phone: Phone) {
// Email is already validated
}
特性への執着
// BEFORE: Method uses another object's data extensively
function calculateShipping(order: Order) {
const address = order.customer.address;
const weight = order.items.reduce((sum, i) => sum + i.weight, 0);
const distance = calculateDistance(address.zip);
return weight * distance * 0.01;
}
// AFTER: Move method to where the data is
class Order {
calculateShipping() {
return this.totalWeight * this.customer.shippingDistance * 0.01;
}
}
リファクタリングの手法
メソッドの抽出
// Identify a code block that does one thing
// Move it to a new method with a descriptive name
// Replace original code with method call
function printReport(data: ReportData) {
// Extract this block...
const header = `Report: ${data.title}\nDate: ${data.date}\n${'='.repeat(40)}`;
console.log(header);
// ...into a method
printHeader(data);
}
条件分岐をポリモーフィズムに置き換える
// BEFORE: Switch on type
function getArea(shape: Shape) {
switch (shape.type) {
case 'circle': return Math.PI * shape.radius ** 2;
case 'rectangle': return shape.width * shape.height;
case 'triangle': return shape.base * shape.height / 2;
}
}
// AFTER: Polymorphic classes
interface Shape {
getArea(): number;
}
class Circle implements Shape {
constructor(private radius: number) {}
getArea() { return Math.PI * this.radius ** 2; }
}
class Rectangle implements Shape {
constructor(private width: number, private height: number) {}
getArea() { return this.width * this.height; }
}
パラメーターオブジェクトの導入
// BEFORE: Too many parameters
function searchProducts(
query: string,
minPrice: number,
maxPrice: number,
category: string,
inStock: boolean,
sortBy: string,
sortOrder: string
) { ... }
// AFTER: Parameter object
interface SearchParams {
query: string;
priceRange: { min: number; max: number };
category?: string;
inStock?: boolean;
sort?: { by: string; order: 'asc' | 'desc' };
}
function searchProducts(params: SearchParams) { ... }
マジックナンバーを定数に置き換える
// BEFORE
if (user.age >= 18 && order.total >= 50) {
applyDiscount(order, 0.1);
}
// AFTER
const MINIMUM_AGE = 18;
const DISCOUNT_THRESHOLD = 50;
const STANDARD_DISCOUNT = 0.1;
if (user.age >= MINIMUM_AGE && order.total >= DISCOUNT_THRESHOLD) {
applyDiscount(order, STANDARD_DISCOUNT);
}
安全なリファクタリングの手順
- テストがあることを確認する - テストがなければ作成します
- 小さな変更を行う - 一度に一つのリファクタリングを行います
- 変更のたびにテストを実行する - リグレッションをすぐに検出します
- こまめにコミットする - 問題が起きた場合に簡単に元に戻せます
- 差分を確認する - 振る舞いが変わっていないことを確認します
リファクタリングのチェックリスト
- 開始前にテストが通っている
- 各変更が小さく、目的が絞られている
- 変更のたびにテストが通っている
- 振る舞いに変更がない(構造のみの変更)
- コードが以前より読みやすい
- コミットメッセージでリファクタリングの内容を説明している
コメント
Markdown 対応