はじめに#
リファクタリングは、ソフトウェアの外部動作を変えずに内部構造を改善する作業です。従来は手作業で行う必要があり、時間がかかる上にミスのリスクも伴いました。Cursorを使えば、AIがコードを分析して改善点を提案し、安全にリファクタリングを実行できます。
本記事では、CursorのAI機能を活用したリファクタリング手法を解説します。この記事を読むことで、以下のことができるようになります。
- 非推奨APIを検出し、最新のAPIに自動更新できる
- 最新の言語仕様やベストプラクティスへの移行提案を受けられる
- デザインパターンを適用してコード構造を改善できる
- パフォーマンス問題を特定し、最適化を実施できる
- Tab補完を活用して連鎖的な修正を効率的に適用できる
実行環境と前提条件#
実行環境#
| 項目 |
要件 |
| オペレーティングシステム |
Windows 10以上、macOS 10.15以上、Ubuntu 20.04以上 |
| Cursor バージョン |
2.3以降(2026年1月時点の安定版) |
| インターネット接続 |
必須(AIモデル利用に必要) |
| 料金プラン |
Hobby(無料)以上 |
前提条件#
- Cursorがインストールされ、アカウントでログイン済みであること
- リファクタリング対象のコードベースが開かれていること
- 基本的なデザインパターンの知識があること(推奨)
- バージョン管理(Git)でコードが管理されていること
期待される結果#
本記事の手順を完了すると、以下の状態になります。
- 古いAPIや非推奨コードを安全に最新化できる
- コードの可読性と保守性が向上する
- パフォーマンスのボトルネックを特定し改善できる
- 継続的なリファクタリングワークフローが確立される
非推奨APIの検出と更新#
ライブラリやフレームワークのアップデートに伴い、使用しているAPIが非推奨になることがあります。Cursorは、これらを検出して最新のAPIに更新する支援をします。
Agentによる非推奨API検出#
Agent(Ctrl+I)を使って、プロジェクト全体の非推奨APIをスキャンできます。
1
2
3
|
指示例:
@Codebase このプロジェクトで使用されている非推奨APIを検出し、
推奨される代替APIへの移行方法を提案してください。
|
Agentは以下のような分析結果を提供します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
Agentの分析結果例:
## 検出された非推奨API
### 1. React関連
- `componentWillMount` (3箇所)
→ `useEffect` または `constructor` に移行推奨
- `componentWillReceiveProps` (5箇所)
→ `static getDerivedStateFromProps` または `useEffect` に移行推奨
### 2. Node.js関連
- `url.parse()` (8箇所)
→ `new URL()` に移行推奨
- `Buffer()` コンストラクタ (2箇所)
→ `Buffer.from()` または `Buffer.alloc()` に移行推奨
### 3. Express関連
- `res.sendfile()` (1箇所)
→ `res.sendFile()` に移行推奨
|
自動更新の実行#
検出された非推奨APIを自動的に更新できます。
1
2
3
|
指示例:
検出された非推奨APIをすべて最新の推奨APIに更新してください。
各ファイルの変更内容を確認させてください。
|
Before: url.parse() の使用#
1
2
3
4
5
6
7
8
9
10
|
const url = require('url');
function parseUrl(urlString) {
const parsed = url.parse(urlString, true);
return {
hostname: parsed.hostname,
pathname: parsed.pathname,
query: parsed.query,
};
}
|
After: URL コンストラクタへの移行#
1
2
3
4
5
6
7
8
|
function parseUrl(urlString) {
const parsed = new URL(urlString);
return {
hostname: parsed.hostname,
pathname: parsed.pathname,
query: Object.fromEntries(parsed.searchParams),
};
}
|
段階的な更新アプローチ#
大規模なコードベースでは、段階的に更新することをお勧めします。
flowchart TD
A[非推奨API検出] --> B[影響範囲の分析]
B --> C{リスク評価}
C -->|低リスク| D[一括更新]
C -->|高リスク| E[段階的更新]
E --> F[ファイル単位で更新]
F --> G[テスト実行]
G --> H{テスト成功?}
H -->|はい| I[次のファイルへ]
H -->|いいえ| J[ロールバック・調整]
J --> F
I --> K{全ファイル完了?}
K -->|いいえ| F
K -->|はい| L[完了]
D --> G最新言語仕様への移行提案#
JavaScript/TypeScript、Python、Javaなど、言語自体も進化しています。Cursorは最新の言語仕様への移行を提案します。
JavaScript/TypeScript のモダン化#
古いJavaScript構文を最新のES仕様に移行します。
1
2
3
4
5
6
|
指示例:
@src/utils このディレクトリのコードをES2023+の最新構文に更新してください。
- var を const/let に
- function を arrow function に(適切な場合)
- Promise チェーンを async/await に
- オプショナルチェーンとNullish coalescingを活用
|
Before: 従来の構文#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
var self = this;
var users = [];
function fetchUsers() {
return fetch('/api/users')
.then(function(response) {
return response.json();
})
.then(function(data) {
if (data && data.users) {
users = data.users;
}
return users;
})
.catch(function(error) {
console.error(error);
return [];
});
}
function getUserName(user) {
if (user && user.profile && user.profile.name) {
return user.profile.name;
}
return 'Unknown';
}
|
After: モダンな構文#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
const users = [];
const fetchUsers = async () => {
try {
const response = await fetch('/api/users');
const data = await response.json();
return data?.users ?? [];
} catch (error) {
console.error(error);
return [];
}
};
const getUserName = (user) => user?.profile?.name ?? 'Unknown';
|
TypeScript の型安全性向上#
型定義を強化してコードの安全性を向上させます。
1
2
3
4
5
|
指示例:
このファイルの型定義を改善してください:
- any型を具体的な型に置き換え
- undefined/nullの可能性がある箇所に適切な型ガードを追加
- ジェネリクスを活用して再利用性を向上
|
Before: 弱い型定義#
1
2
3
4
5
6
7
8
9
10
11
|
function processData(data: any): any {
const result = [];
for (const item of data) {
result.push({
id: item.id,
name: item.name,
value: item.value * 2,
});
}
return result;
}
|
After: 強い型定義#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
interface InputItem {
id: string;
name: string;
value: number;
}
interface OutputItem {
id: string;
name: string;
value: number;
}
function processData(data: InputItem[]): OutputItem[] {
return data.map((item) => ({
id: item.id,
name: item.name,
value: item.value * 2,
}));
}
|
デザインパターンの適用#
Cursorは、コードの構造を分析してデザインパターンの適用を提案します。
コード構造の分析#
まず、現在のコード構造を分析します。
1
2
3
4
|
指示例:
@src/services/payment.service.ts このファイルを分析して、
適用できるデザインパターンを提案してください。
現在の問題点と改善後のメリットも説明してください。
|
Strategy パターンの適用例#
条件分岐が多いコードにStrategyパターンを適用します。
Before: 条件分岐による実装#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class PaymentService {
processPayment(type: string, amount: number): PaymentResult {
if (type === 'credit_card') {
// クレジットカード処理(50行)
const fee = amount * 0.03;
// ...多数の処理
return { success: true, fee };
} else if (type === 'bank_transfer') {
// 銀行振込処理(40行)
const fee = 300;
// ...多数の処理
return { success: true, fee };
} else if (type === 'e_wallet') {
// 電子マネー処理(45行)
const fee = amount * 0.01;
// ...多数の処理
return { success: true, fee };
}
throw new Error('Unknown payment type');
}
}
|
After: Strategy パターン適用#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
// 支払い戦略インターフェース
interface PaymentStrategy {
process(amount: number): PaymentResult;
calculateFee(amount: number): number;
}
// クレジットカード戦略
class CreditCardStrategy implements PaymentStrategy {
process(amount: number): PaymentResult {
const fee = this.calculateFee(amount);
// クレジットカード固有の処理
return { success: true, fee };
}
calculateFee(amount: number): number {
return amount * 0.03;
}
}
// 銀行振込戦略
class BankTransferStrategy implements PaymentStrategy {
process(amount: number): PaymentResult {
const fee = this.calculateFee(amount);
// 銀行振込固有の処理
return { success: true, fee };
}
calculateFee(amount: number): number {
return 300;
}
}
// 電子マネー戦略
class EWalletStrategy implements PaymentStrategy {
process(amount: number): PaymentResult {
const fee = this.calculateFee(amount);
// 電子マネー固有の処理
return { success: true, fee };
}
calculateFee(amount: number): number {
return amount * 0.01;
}
}
// コンテキストクラス
class PaymentService {
private strategies: Map<string, PaymentStrategy>;
constructor() {
this.strategies = new Map([
['credit_card', new CreditCardStrategy()],
['bank_transfer', new BankTransferStrategy()],
['e_wallet', new EWalletStrategy()],
]);
}
processPayment(type: string, amount: number): PaymentResult {
const strategy = this.strategies.get(type);
if (!strategy) {
throw new Error(`Unknown payment type: ${type}`);
}
return strategy.process(amount);
}
}
|
その他のパターン適用例#
Cursorは様々なデザインパターンの適用を支援します。
| パターン |
適用シーン |
指示例 |
| Factory |
オブジェクト生成の複雑化 |
「このswitch文をFactoryパターンでリファクタリングして」 |
| Observer |
イベント通知の散在 |
「このコールバック地獄をObserverパターンに変換して」 |
| Decorator |
機能の動的追加 |
「ログ機能をDecoratorパターンで追加して」 |
| Singleton |
グローバルな状態管理 |
「このクラスをSingletonパターンに変換して」 |
| Repository |
データアクセスの抽象化 |
「直接DBアクセスをRepositoryパターンで抽象化して」 |
パフォーマンス改善提案#
Cursorは、パフォーマンス問題を検出し、改善提案を行います。
パフォーマンス分析の依頼#
1
2
3
4
5
6
7
|
指示例:
@src/services/data.service.ts このファイルのパフォーマンス問題を分析し、
改善提案をしてください。特に以下の観点で確認してください:
- N+1クエリ問題
- 不要なループ処理
- メモリリーク
- キャッシュの活用
|
N+1問題の解決#
Before: N+1クエリ問題#
1
2
3
4
5
6
7
8
9
10
11
12
|
async function getOrdersWithProducts(userId: string) {
const orders = await orderRepository.find({ where: { userId } });
// N+1問題:注文ごとにクエリが発行される
for (const order of orders) {
order.products = await productRepository.find({
where: { orderId: order.id },
});
}
return orders;
}
|
After: JOINによる一括取得#
1
2
3
4
5
6
|
async function getOrdersWithProducts(userId: string) {
return orderRepository.find({
where: { userId },
relations: ['products'],
});
}
|
ループ処理の最適化#
Before: 非効率なループ#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function findDuplicates(items: Item[]): Item[] {
const duplicates: Item[] = [];
for (let i = 0; i < items.length; i++) {
for (let j = i + 1; j < items.length; j++) {
if (items[i].id === items[j].id) {
if (!duplicates.find(d => d.id === items[i].id)) {
duplicates.push(items[i]);
}
}
}
}
return duplicates;
}
|
After: Setを活用した最適化#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
function findDuplicates(items: Item[]): Item[] {
const seen = new Set<string>();
const duplicateIds = new Set<string>();
for (const item of items) {
if (seen.has(item.id)) {
duplicateIds.add(item.id);
} else {
seen.add(item.id);
}
}
return items.filter(item => duplicateIds.has(item.id));
}
|
メモ化によるキャッシュ#
1
2
3
|
指示例:
この計算処理が頻繁に呼び出されています。
適切なメモ化を追加して、パフォーマンスを改善してください。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
// メモ化ユーティリティ
function memoize<T extends (...args: any[]) => any>(fn: T): T {
const cache = new Map<string, ReturnType<T>>();
return ((...args: Parameters<T>): ReturnType<T> => {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key)!;
}
const result = fn(...args);
cache.set(key, result);
return result;
}) as T;
}
// 使用例
const calculateComplexValue = memoize((input: number): number => {
// 重い計算処理
return expensiveCalculation(input);
});
|
Tab補完を活用した連鎖的な修正#
リファクタリングでは、1つの変更が複数の箇所に波及することがあります。CursorのTab補完は、この連鎖的な修正を効率化します。
連鎖修正のワークフロー#
flowchart LR
A[1箇所を修正] --> B[Tab補完が<br/>関連修正を提案]
B --> C[Tabで適用]
C --> D[次の修正箇所へ<br/>自動ジャンプ]
D --> E{修正完了?}
E -->|いいえ| B
E -->|はい| F[完了]実践例: 関数名の変更#
関数名を変更すると、Tab補完が呼び出し元の修正を提案します。
- 関数名を
getUser から fetchUserById に変更
1
2
3
4
5
6
7
8
9
|
// 変更前
async function getUser(id: string): Promise<User> {
return userRepository.findOne({ where: { id } });
}
// 変更後(手動で変更)
async function fetchUserById(id: string): Promise<User> {
return userRepository.findOne({ where: { id } });
}
|
- Tab補完が関連箇所の修正を提案
1
2
3
|
// Tab補完による提案(ゴーストテキスト)
// 呼び出し元ファイルに自動ジャンプ
const user = await fetchUserById(userId); // getUser → fetchUserById
|
- Tabキーで適用し、次の修正箇所へジャンプ
型定義の変更と連鎖修正#
インターフェースのプロパティを変更した場合も、Tab補完が活躍します。
1
2
3
4
5
6
7
8
|
// 型定義を変更
interface User {
id: string;
// name を firstName と lastName に分割
firstName: string;
lastName: string;
email: string;
}
|
Tab補完により、user.name を使用している箇所に対して以下のような修正が提案されます。
1
2
3
|
// Tab補完の提案
// user.name → `${user.firstName} ${user.lastName}`
const displayName = `${user.firstName} ${user.lastName}`;
|
複数ファイルへの波及#
Tab補完は、開いているファイルだけでなく、プロジェクト全体の関連箇所を認識します。
| 変更内容 |
Tab補完の提案範囲 |
| 関数名変更 |
すべての呼び出し元 |
| プロパティ追加/削除 |
すべての使用箇所 |
| インポートパス変更 |
すべてのインポート文 |
| 定数値変更 |
すべての参照箇所 |
リファクタリングワークフロー#
安全かつ効率的にリファクタリングを行うためのワークフローを紹介します。
事前準備#
リファクタリングを始める前に、以下を確認します。
- テストの存在確認: リファクタリング対象のコードにテストがあるか
- チェックポイント作成: 大きな変更前にチェックポイントを作成
- 影響範囲の把握: 変更が影響する範囲を確認
1
2
3
|
指示例:
リファクタリングを始める前に、チェックポイントを作成してください。
その後、@src/services/order.service.ts の影響範囲を分析してください。
|
段階的リファクタリング#
一度に大きな変更を加えるのではなく、小さな単位で進めます。
1
2
3
4
5
6
7
8
9
10
11
|
段階的リファクタリングの指示例:
ステップ1:
まず extractMethod リファクタリングで、
calculateTotal 関数内の税計算ロジックを別関数に抽出してください。
ステップ2:
抽出した税計算関数のテストを追加してください。
ステップ3:
税計算関数を TaxCalculator クラスに移動してください。
|
変更の検証#
各ステップ後にテストを実行して、動作を検証します。
1
2
3
|
指示例:
ターミナルでテストを実行してください。
失敗したテストがあれば原因を分析して修正してください。
|
よくあるトラブルと対処法#
リファクタリング後にテストが失敗する#
テスト失敗の原因を特定し、修正します。
1
2
3
4
5
6
|
指示例:
以下のテストが失敗しています:
「Expected: 100, Received: 103」
テストコードとリファクタリング後のコードを比較して、
原因を特定し修正してください。
|
循環参照が発生する#
モジュール間の依存関係を整理します。
1
2
3
4
5
|
指示例:
リファクタリング後に循環参照エラーが発生しました:
"Circular dependency detected: A → B → C → A"
依存関係を分析し、循環を解消する方法を提案してください。
|
型エラーが大量に発生する#
段階的に型エラーを解消します。
1
2
3
|
指示例:
リファクタリング後に型エラーが50件発生しています。
エラーをカテゴリ別に分類し、優先度の高いものから順に修正してください。
|
まとめ#
Cursorを活用したリファクタリングは、コード品質を効率的に向上させるAI駆動開発の重要な手法です。本記事で解説した内容をまとめます。
- 非推奨APIの検出と更新: Agentによる自動検出と最新APIへの移行支援
- 最新言語仕様への移行: モダンな構文と型安全性の向上
- デザインパターンの適用: コード構造の分析とパターン適用の提案
- パフォーマンス改善: N+1問題の解決、ループ最適化、キャッシュ活用
- Tab補完による連鎖修正: 関連箇所への自動ジャンプと修正提案
これらのテクニックを組み合わせることで、安全かつ効率的にコードベースの品質を向上させられます。リファクタリングは一度きりの作業ではなく、継続的に行うことでコードの健全性を維持できます。
参考リンク#