リファクタリングガイド
この包括的なガイドでは、Traeでコード品質と保守性を向上させるためのリファクタリング技術、ツール、ベストプラクティスについて説明します。
リファクタリングの概要
リファクタリングとは?
リファクタリングとは、外部動作を変更することなく既存のコードを再構築するプロセスです。目標は、機能を保持しながらコードの可読性を向上させ、複雑さを軽減し、保守性を高めることです。
リファクタリングの利点
- コード品質の向上: より清潔で読みやすいコード
- 保守性の向上: 変更や拡張が容易
- 技術的負債の削減: コードの臭いやアンチパターンの排除
- パフォーマンスの向上: 最適化されたアルゴリズムとデータ構造
- 開発者の生産性向上: 理解しやすく作業しやすい
リファクタリングのタイミング
- 新機能を追加する前
- バグを修正する際
- コードレビュー中
- コードが理解困難になった時
- パフォーマンス問題が発生した時
- 定期的なメンテナンスの一環として
Traeのリファクタリングツール
組み込みリファクタリング機能
シンボルの名前変更
typescript
// リファクタリング前
function calculateTotal(items: Item[]): number {
let sum = 0;
for (const item of items) {
sum += item.price;
}
return sum;
}
// 'sum'を'total'に名前変更後
function calculateTotal(items: Item[]): number {
let total = 0;
for (const item of items) {
total += item.price;
}
return total;
}使用方法:
- シンボルを右クリック → "シンボルの名前変更" (F2)
- 新しい名前を入力
- Enterキーを押してすべてのファイルに変更を適用
メソッドの抽出
typescript
// 抽出前
class OrderProcessor {
processOrder(order: Order): void {
// 注文の検証
if (!order.items || order.items.length === 0) {
throw new Error('注文にはアイテムが必要です');
}
if (!order.customer) {
throw new Error('注文には顧客が必要です');
}
// 合計の計算
let total = 0;
for (const item of order.items) {
total += item.price * item.quantity;
}
order.total = total;
// 注文の保存
this.saveOrder(order);
}
}
// メソッド抽出後
class OrderProcessor {
processOrder(order: Order): void {
this.validateOrder(order);
this.calculateTotal(order);
this.saveOrder(order);
}
private validateOrder(order: Order): void {
if (!order.items || order.items.length === 0) {
throw new Error('注文にはアイテムが必要です');
}
if (!order.customer) {
throw new Error('注文には顧客が必要です');
}
}
private calculateTotal(order: Order): void {
let total = 0;
for (const item of order.items) {
total += item.price * item.quantity;
}
order.total = total;
}
private saveOrder(order: Order): void {
// 注文保存ロジック
}
}使用方法:
- 抽出したいコードを選択
- 右クリック → "メソッドの抽出" (Ctrl+Shift+M)
- メソッド名を入力
- Enterキーを押して適用
変数の抽出
typescript
// 抽出前
function calculateDiscount(order: Order): number {
return order.total * 0.1 + (order.items.length > 5 ? order.total * 0.05 : 0);
}
// 変数抽出後
function calculateDiscount(order: Order): number {
const baseDiscount = order.total * 0.1;
const bulkDiscount = order.items.length > 5 ? order.total * 0.05 : 0;
return baseDiscount + bulkDiscount;
}インターフェースの抽出
typescript
// 抽出前
class EmailService {
sendEmail(to: string, subject: string, body: string): void {
// メール送信ロジック
}
sendBulkEmail(recipients: string[], subject: string, body: string): void {
// 一括メール送信ロジック
}
}
// インターフェース抽出後
interface IEmailService {
sendEmail(to: string, subject: string, body: string): void;
sendBulkEmail(recipients: string[], subject: string, body: string): void;
}
class EmailService implements IEmailService {
sendEmail(to: string, subject: string, body: string): void {
// メール送信ロジック
}
sendBulkEmail(recipients: string[], subject: string, body: string): void {
// 一括メール送信ロジック
}
}高度なリファクタリング機能
クラスの移動
typescript
// 移動前 - utils/helpers.ts
export class ValidationHelper {
static validateEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
}
// 移動後 - validators/emailValidator.ts
export class EmailValidator {
static validate(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
}ファイルの移動とリネーム
typescript
// 移動前のファイル構造
src/
utils/
stringHelpers.ts
dateHelpers.ts
// 移動後のファイル構造
src/
helpers/
string/
stringUtils.ts
date/
dateUtils.tsリファクタリングパターン
1. 長いメソッドの分割
問題: メソッドが長すぎて理解困難
typescript
// 問題のあるコード
class ReportGenerator {
generateReport(data: any[]): string {
// データの検証(20行)
if (!data || data.length === 0) {
throw new Error('データが必要です');
}
// データの変換(30行)
const transformedData = data.map(item => {
// 複雑な変換ロジック
});
// レポートの生成(40行)
let report = '<html><body>';
// HTMLの生成ロジック
report += '</body></html>';
return report;
}
}解決策: メソッドを小さな単位に分割
typescript
// 改善されたコード
class ReportGenerator {
generateReport(data: any[]): string {
this.validateData(data);
const transformedData = this.transformData(data);
return this.buildHtmlReport(transformedData);
}
private validateData(data: any[]): void {
if (!data || data.length === 0) {
throw new Error('データが必要です');
}
}
private transformData(data: any[]): any[] {
return data.map(item => this.transformItem(item));
}
private transformItem(item: any): any {
// 変換ロジック
return item;
}
private buildHtmlReport(data: any[]): string {
const header = this.buildHeader();
const body = this.buildBody(data);
const footer = this.buildFooter();
return `${header}${body}${footer}`;
}
private buildHeader(): string {
return '<html><head><title>レポート</title></head><body>';
}
private buildBody(data: any[]): string {
// ボディの生成ロジック
return '';
}
private buildFooter(): string {
return '</body></html>';
}
}2. 重複コードの排除
問題: 同じロジックが複数箇所に存在
typescript
// 重複のあるコード
class UserService {
createUser(userData: any): User {
// 検証ロジック
if (!userData.email || !userData.email.includes('@')) {
throw new Error('有効なメールアドレスが必要です');
}
if (!userData.password || userData.password.length < 8) {
throw new Error('パスワードは8文字以上である必要があります');
}
return new User(userData);
}
updateUser(id: string, userData: any): User {
// 同じ検証ロジック
if (!userData.email || !userData.email.includes('@')) {
throw new Error('有効なメールアドレスが必要です');
}
if (!userData.password || userData.password.length < 8) {
throw new Error('パスワードは8文字以上である必要があります');
}
// 更新ロジック
return this.updateUserData(id, userData);
}
}解決策: 共通ロジックを抽出
typescript
// 改善されたコード
class UserService {
createUser(userData: any): User {
this.validateUserData(userData);
return new User(userData);
}
updateUser(id: string, userData: any): User {
this.validateUserData(userData);
return this.updateUserData(id, userData);
}
private validateUserData(userData: any): void {
this.validateEmail(userData.email);
this.validatePassword(userData.password);
}
private validateEmail(email: string): void {
if (!email || !email.includes('@')) {
throw new Error('有効なメールアドレスが必要です');
}
}
private validatePassword(password: string): void {
if (!password || password.length < 8) {
throw new Error('パスワードは8文字以上である必要があります');
}
}
private updateUserData(id: string, userData: any): User {
// 更新ロジック
return new User(userData);
}
}3. 大きなクラスの分割
問題: クラスが多すぎる責任を持っている
typescript
// 問題のあるコード
class UserManager {
// ユーザー管理
createUser(userData: any): User { /* ... */ }
updateUser(id: string, userData: any): User { /* ... */ }
deleteUser(id: string): void { /* ... */ }
// 認証
login(email: string, password: string): string { /* ... */ }
logout(token: string): void { /* ... */ }
// メール送信
sendWelcomeEmail(user: User): void { /* ... */ }
sendPasswordResetEmail(email: string): void { /* ... */ }
// レポート生成
generateUserReport(): string { /* ... */ }
exportUserData(): string { /* ... */ }
}解決策: 責任に基づいてクラスを分割
typescript
// 改善されたコード
class UserService {
createUser(userData: any): User { /* ... */ }
updateUser(id: string, userData: any): User { /* ... */ }
deleteUser(id: string): void { /* ... */ }
}
class AuthenticationService {
login(email: string, password: string): string { /* ... */ }
logout(token: string): void { /* ... */ }
}
class EmailService {
sendWelcomeEmail(user: User): void { /* ... */ }
sendPasswordResetEmail(email: string): void { /* ... */ }
}
class UserReportService {
generateUserReport(): string { /* ... */ }
exportUserData(): string { /* ... */ }
}
// コーディネーターとして使用
class UserManager {
constructor(
private userService: UserService,
private authService: AuthenticationService,
private emailService: EmailService,
private reportService: UserReportService
) {}
async registerUser(userData: any): Promise<User> {
const user = this.userService.createUser(userData);
await this.emailService.sendWelcomeEmail(user);
return user;
}
}コードの臭いと対処法
1. 長いパラメータリスト
問題:
typescript
function createUser(
firstName: string,
lastName: string,
email: string,
phone: string,
address: string,
city: string,
country: string,
zipCode: string
): User {
// ...
}解決策: パラメータオブジェクトの導入
typescript
interface UserData {
firstName: string;
lastName: string;
email: string;
phone: string;
address: {
street: string;
city: string;
country: string;
zipCode: string;
};
}
function createUser(userData: UserData): User {
// ...
}2. 特徴の妬み(Feature Envy)
問題: クラスが他のクラスのデータに過度に依存
typescript
class Order {
constructor(private customer: Customer) {}
calculateDiscount(): number {
// 顧客データに過度に依存
if (this.customer.isPremium() &&
this.customer.getYearsActive() > 5 &&
this.customer.getTotalPurchases() > 10000) {
return 0.15;
}
return 0.05;
}
}解決策: ロジックを適切なクラスに移動
typescript
class Customer {
isPremium(): boolean { /* ... */ }
getYearsActive(): number { /* ... */ }
getTotalPurchases(): number { /* ... */ }
getDiscountRate(): number {
if (this.isPremium() &&
this.getYearsActive() > 5 &&
this.getTotalPurchases() > 10000) {
return 0.15;
}
return 0.05;
}
}
class Order {
constructor(private customer: Customer) {}
calculateDiscount(): number {
return this.customer.getDiscountRate();
}
}3. データクラス
問題: データのみを持つクラス
typescript
class Product {
constructor(
public name: string,
public price: number,
public category: string
) {}
}
// 他の場所でロジックが散在
function calculateTax(product: Product): number {
if (product.category === 'food') {
return product.price * 0.05;
}
return product.price * 0.1;
}解決策: 関連するロジックをクラスに移動
typescript
class Product {
constructor(
private name: string,
private price: number,
private category: string
) {}
getName(): string {
return this.name;
}
getPrice(): number {
return this.price;
}
getCategory(): string {
return this.category;
}
calculateTax(): number {
if (this.category === 'food') {
return this.price * 0.05;
}
return this.price * 0.1;
}
getTotalPrice(): number {
return this.price + this.calculateTax();
}
}自動リファクタリング
設定
json
{
"refactoring": {
"autoSave": true,
"confirmBeforeRefactor": true,
"updateImports": true,
"formatAfterRefactor": true,
"rules": {
"maxMethodLength": 20,
"maxParameterCount": 4,
"maxClassLength": 200
}
}
}カスタムリファクタリングルール
typescript
// .trae/refactoring-rules.ts
export const customRules = {
// 長いメソッドの検出
longMethod: {
maxLines: 15,
action: 'suggest-extract-method'
},
// 重複コードの検出
duplicateCode: {
minLines: 5,
action: 'suggest-extract-function'
},
// 未使用のインポートの削除
unusedImports: {
action: 'auto-remove'
}
};ベストプラクティス
1. 段階的なリファクタリング
- 小さな変更を積み重ねる
- 各ステップでテストを実行
- 一度に一つの問題に集中
2. テストファーストアプローチ
typescript
// リファクタリング前にテストを作成
describe('UserService', () => {
test('should create user with valid data', () => {
const userData = {
email: 'test@example.com',
password: 'password123'
};
const user = userService.createUser(userData);
expect(user.email).toBe(userData.email);
});
});3. コードレビューの活用
- リファクタリング前後でレビューを実施
- チーム全体でコード品質基準を共有
- 継続的な改善を心がける
4. ドキュメントの更新
typescript
/**
* ユーザーサービス
*
* ユーザーの作成、更新、削除を管理します。
*
* @example
* ```typescript
* const userService = new UserService();
* const user = userService.createUser({
* email: 'user@example.com',
* password: 'securePassword'
* });
* ```
*/
class UserService {
/**
* 新しいユーザーを作成します
*
* @param userData - ユーザーデータ
* @returns 作成されたユーザー
* @throws {Error} 無効なデータの場合
*/
createUser(userData: UserData): User {
// 実装
}
}トラブルシューティング
よくある問題
リファクタリング後のビルドエラー
- インポートパスの確認
- 型定義の更新
- 依存関係の解決
テストの失敗
- テストケースの更新
- モックの調整
- アサーションの見直し
パフォーマンスの低下
- プロファイリングの実行
- ボトルネックの特定
- 最適化の適用
デバッグ支援
typescript
// リファクタリング前後の比較
console.log('リファクタリング前:', {
methodCount: oldClass.methods.length,
lineCount: oldClass.lineCount,
complexity: oldClass.cyclomaticComplexity
});
console.log('リファクタリング後:', {
methodCount: newClass.methods.length,
lineCount: newClass.lineCount,
complexity: newClass.cyclomaticComplexity
});まとめ
リファクタリングは継続的なプロセスです。定期的にコードを見直し、改善の機会を見つけることで、長期的にメンテナンスしやすいコードベースを維持できます。
Traeの強力なリファクタリングツールを活用して、効率的にコード品質を向上させましょう。