はじめに
「動くけど、このコード読みにくいな...」
「後から見て何やってるかわからなくなりそう...」
こんな経験、ありませんか?
リファクタリングは大事だけど、手作業でやると時間がかかるし、壊しそうで怖い。そんなときこそ Claude Codeの出番 です。
今回は、Claude Codeを使って既存コードをキレイにリファクタリングする方法を解説します。
リファクタリングとは
動作を変えずに、コードの内部構造を改善すること です。
- 読みやすくする
- 保守しやすくする
- 重複を減らす
- 適切な命名に変える
Claude Codeはコードの意図を理解した上で改善してくれるので、安心してお任せできます。
実践1:長すぎる関数を分割する
Before:1つの関数に処理が詰め込まれている
async function processOrder(order) {
// バリデーション
if (!order.items || order.items.length === 0) {
throw new Error('注文アイテムがありません');
}
if (!order.userId) {
throw new Error('ユーザーIDがありません');
}
for (const item of order.items) {
const stock = await db.query('SELECT stock FROM products WHERE id = ?', [item.productId]);
if (stock[0].stock < item.quantity) {
throw new Error(`${item.productId}の在庫が不足しています`);
}
}
// 合計金額計算
let total = 0;
for (const item of order.items) {
const product = await db.query('SELECT price FROM products WHERE id = ?', [item.productId]);
total += product[0].price * item.quantity;
}
if (order.couponCode) {
const coupon = await db.query('SELECT discount FROM coupons WHERE code = ?', [order.couponCode]);
if (coupon.length > 0) {
total = total * (1 - coupon[0].discount / 100);
}
}
const tax = total * 0.1;
total = total + tax;
// 注文保存
const orderId = await db.query('INSERT INTO orders (user_id, total) VALUES (?, ?)', [order.userId, total]);
for (const item of order.items) {
await db.query('INSERT INTO order_items (order_id, product_id, quantity) VALUES (?, ?, ?)',
[orderId, item.productId, item.quantity]);
await db.query('UPDATE products SET stock = stock - ? WHERE id = ?',
[item.quantity, item.productId]);
}
// メール送信
const user = await db.query('SELECT email FROM users WHERE id = ?', [order.userId]);
await sendEmail(user[0].email, '注文確認', `注文ID: ${orderId}, 合計: ¥${total}`);
return { orderId, total };
}
プロンプト
> @src/services/order.ts のprocessOrder関数が長すぎる。
> 責務ごとに関数を分割してリファクタリングして。
> 動作は変えないで。
After:責務ごとに分割
async function validateOrder(order) {
if (!order.items || order.items.length === 0) {
throw new Error('注文アイテムがありません');
}
if (!order.userId) {
throw new Error('ユーザーIDがありません');
}
for (const item of order.items) {
const stock = await db.query('SELECT stock FROM products WHERE id = ?', [item.productId]);
if (stock[0].stock < item.quantity) {
throw new Error(`${item.productId}の在庫が不足しています`);
}
}
}
async function calculateTotal(items, couponCode) {
let total = 0;
for (const item of items) {
const product = await db.query('SELECT price FROM products WHERE id = ?', [item.productId]);
total += product[0].price * item.quantity;
}
if (couponCode) {
const coupon = await db.query('SELECT discount FROM coupons WHERE code = ?', [couponCode]);
if (coupon.length > 0) {
total = total * (1 - coupon[0].discount / 100);
}
}
const tax = total * 0.1;
return total + tax;
}
async function saveOrder(userId, total, items) {
const orderId = await db.query('INSERT INTO orders (user_id, total) VALUES (?, ?)', [userId, total]);
for (const item of items) {
await db.query('INSERT INTO order_items (order_id, product_id, quantity) VALUES (?, ?, ?)',
[orderId, item.productId, item.quantity]);
await db.query('UPDATE products SET stock = stock - ? WHERE id = ?',
[item.quantity, item.productId]);
}
return orderId;
}
async function notifyUser(userId, orderId, total) {
const user = await db.query('SELECT email FROM users WHERE id = ?', [userId]);
await sendEmail(user[0].email, '注文確認', `注文ID: ${orderId}, 合計: ¥${total}`);
}
async function processOrder(order) {
await validateOrder(order);
const total = await calculateTotal(order.items, order.couponCode);
const orderId = await saveOrder(order.userId, total, order.items);
await notifyUser(order.userId, orderId, total);
return { orderId, total };
}
processOrder がスッキリして、各処理の意図が明確になりました。
実践2:ネストが深いコードを改善
Before
def get_user_display_name(user):
if user is not None:
if user.profile is not None:
if user.profile.display_name is not None:
if len(user.profile.display_name) > 0:
return user.profile.display_name
else:
return user.username
else:
return user.username
else:
return user.username
else:
return "Unknown"
プロンプト
> この関数のネストが深すぎる。早期リターンを使ってフラットにして。
After
def get_user_display_name(user):
if user is None:
return "Unknown"
if not user.profile or not user.profile.display_name:
return user.username
if len(user.profile.display_name) == 0:
return user.username
return user.profile.display_name
ネストが解消されて、一目で読めるようになりました。
実践3:変数名・関数名の改善
プロンプト
> @src/utils/calc.ts の変数名と関数名がわかりにくい。
> 処理内容がわかる名前に変更して。
> プロジェクト全体で使われている箇所も一括で更新して。
Before
function calc(d: any[], t: string) {
let r = 0;
for (const x of d) {
if (x.tp === t) {
r += x.v * x.q;
}
}
return r;
}
After
function calculateTotalByType(items: OrderItem[], targetType: string): number {
let total = 0;
for (const item of items) {
if (item.type === targetType) {
total += item.price * item.quantity;
}
}
return total;
}
Claude Codeは プロジェクト全体を検索して、呼び出し元も一括で更新 してくれます。
実践4:重複コードの統合
プロンプト
> このプロジェクトで似たような処理が重複している箇所を見つけて、
> 共通関数にまとめて。
Claude Codeがプロジェクト全体をスキャンして、重複を特定します。
Claude Codeの回答例
以下の3箇所で同じような日付フォーマット処理が重複していました:
1. src/pages/Dashboard.tsx (42行目)
2. src/pages/Report.tsx (28行目)
3. src/components/DateDisplay.tsx (15行目)
共通の formatDate 関数を src/utils/date.ts に作成し、
各ファイルからインポートするように変更しました。
実践5:コード全体の品質チェック
プロンプト
> @src/services/ ディレクトリのコードをレビューして。
> 改善できるポイントを優先度順にリストアップして。
Claude Codeの回答例
レビュー結果(優先度順):
【高】
1. auth.ts: パスワードが平文で比較されている → bcryptを使用すべき
2. payment.ts: エラーハンドリングが不足 → try-catchの追加が必要
【中】
3. user.ts: N+1クエリが発生している → JOINまたはIN句に変更
4. order.ts: マジックナンバーが散在 → 定数に切り出し
【低】
5. email.ts: 同期処理でメール送信 → 非同期キューの使用を推奨
6. logger.ts: console.logの直接使用 → ロガーライブラリの導入
修正しますか?
> 高と中の項目を修正して
リファクタリングのプロンプト集
構造の改善
> この関数を小さな関数に分割して
> このクラスが大きすぎるので、責務ごとに分割して
> コールバック地獄をasync/awaitに書き換えて
> このif文のネストを早期リターンでフラットにして
命名の改善
> この変数名と関数名をわかりやすい名前に変えて
> プロジェクト全体で統一されていない命名を揃えて
パフォーマンス改善
> このループ処理を効率的に書き換えて
> N+1クエリを修正して
> 不要な再レンダリングを防ぐようにして
モダン化
> var宣言をconst/letに書き換えて
> classコンポーネントをfunction + hooksに書き換えて
> Promiseチェーンをasync/awaitに書き換えて
> CommonJSのrequireをES Modules importに変換して
リファクタリング時の注意点
1. 事前にテストがあるか確認
> テストがあるか確認して。なければ先にテストを書いて。
テストがあれば、リファクタリング後に動作が変わっていないことを確認できます。
2. 段階的にやる
一気に全部変えず、1つずつ確認しながら進める のが安全です。
> まず関数の分割だけやって
> (確認後)
> 次に命名を改善して
3. git commitを挟む
> ここまでの変更をコミットして
リファクタリングのステップごとにコミットしておけば、問題が起きても戻れます。
4. planモードで確認
大きなリファクタリングの前に、計画を確認しましょう。
# Shift+Tab で planモードに切り替え
> src/services/ 全体をリファクタリングする計画を立てて
planモードではコードを変更せず、計画だけ提示してくれます。
まとめ
| リファクタリング | プロンプト例 |
|---|---|
| 関数分割 | 「この関数を責務ごとに分割して」 |
| ネスト解消 | 「早期リターンでフラットにして」 |
| 命名改善 | 「変数名をわかりやすく変えて」 |
| 重複削除 | 「重複コードを見つけて共通化して」 |
| 品質レビュー | 「コードをレビューして改善点を挙げて」 |
| モダン化 | 「async/awaitに書き換えて」 |
リファクタリングはAIが最も得意な作業の1つです。 面倒だと後回しにしがちな改善作業を、Claude Codeにどんどんお任せしましょう。
次回予告
次の記事では、Claude Codeでテストコードを作成する 方法を解説します。
テストを書くのが苦手な方、Claude Codeなら一瞬です!
シリーズ一覧
- Claude Codeとは?概要・できること
- Claude Codeのインストールと初期設定
- 基本的な使い方・コマンド一覧
- Claude Codeでコード生成してみよう
- Claude Codeでバグ修正・デバッグ
- 👉 Claude Codeでリファクタリング(本記事)
- Claude Codeでテストコード作成
- Claude Codeでgit操作を効率化
- CLAUDE.mdを活用したプロジェクト設定
- Claude Code活用のベストプラクティス
著者: @kotaro_ai_lab
AI駆動開発やテック情報を毎日発信しています。フォローお気軽にどうぞ!