mcpField
R
refactoring

refactoring

github.com/mgd34msu開発659 回インストール10 週間前に更新
リポジトリ

refactoring について

refactoringは、外部から見える動作を変えずにコード構造と保守性を改善する開発者向けスキルです。抽出、命名、型の改善、依存関係の分離を扱う手順を示します。

主な機能

  1. 01コードの問題点と依存関係の分析
  2. 02関数とコンポーネントの抽出
  3. 03命名とファイル構成の改善
  4. 04条件分岐の簡素化
  5. 05型の強化と依存関係の分離

活用シーン

  1. 01重複コードの解消
  2. 02大きすぎる関数やコンポーネントの分割
  3. 03TypeScriptでのany使用の削減
  4. 04複雑な条件分岐やファイル構成の整理

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/mgd34msu/goodvibes-plugin --skill refactoring

インストール後、Claude Code で/refactoringを入力して読み込まれたか確認します。

同梱ファイルreferences/refactoring-patterns.mdscripts/validate-refactoring.sh

詳細

リソース

scripts/
  validate-refactoring.sh
references/
  refactoring-patterns.md

リファクタリング品質スキル

このスキルでは、GoodVibes の高精度ツールを使用して、安全かつ体系的にコードをリファクタリングする方法を説明します。リファクタリングは、外部から見た動作を変えずにコードの構造と保守性を向上させるため、今後の開発を迅速化し、バグを減らせます。

このスキルを使用するタイミング

次のような場合に、このスキルを読み込みます。

  • コードの重複をなくす必要がある
  • 関数やコンポーネントが大きくなりすぎている
  • 型安全性を高める必要がある(any の使用を減らす)
  • 条件分岐が複雑になり、理解しにくくなっている
  • ファイルやフォルダーの構成を再編成する必要がある
  • 依存関係が密結合しており、依存性逆転が必要である
  • データベースのスキーマやクエリを最適化する必要がある
  • コードの不吉な臭いが開発を遅らせている

トリガーフレーズ:「このコードをリファクタリング」「重複を減らす」「関数を抽出」「条件分岐を簡素化」「型を改善」「再編成」「コードを整理」。

基本的なワークフロー

フェーズ 1:分析 — 現状を把握する

リファクタリングを始める前に、現在のコード構造を把握し、リファクタリングの機会を特定します。

ステップ 1.1:コードの不吉な臭いを特定する

discover を使用して、リファクタリングが必要な一般的なコードの不吉な臭いを見つけます。

discover:
  queries:
    # Large files (> 300 lines)
    - id: large_files
      type: glob
      patterns: ["src/**/*.{ts,tsx,js,jsx}"]
    # Code duplication
    - id: duplicate_patterns
      type: grep
      pattern: "(function|const|class)\\s+\\w+"
      glob: "**/*.{ts,tsx,js,jsx}"
    # Type safety issues
    - id: any_usage
      type: grep
      pattern: ":\\s*any(\\s|;|,|\\))"
      glob: "**/*.{ts,tsx}"
    # Complex conditionals
    - id: nested_conditions
      type: grep
      pattern: "if\\s*\\(.*if\\s*\\("
      glob: "**/*.{ts,tsx,js,jsx}"
  verbosity: files_only

ここから分かること:

  • 分割が必要なファイル
  • 繰り返されているコードパターン
  • 型安全性の不足
  • 複雑な条件分岐
ステップ 1.2:依存関係を把握する

precision_grep を使用して、コードベース全体でコードがどのように使用されているかを把握します。

precision_grep:
  queries:
    - id: function_usage
      pattern: "importFunctionName\\("
      glob: "**/*.{ts,tsx,js,jsx}"
  output:
    format: locations
  verbosity: standard

重要な理由:

  • リファクタリングでは、すべての呼び出し箇所を更新する必要があります
  • 破壊的変更を事前に特定する必要があります
  • 未使用のコードは安全に削除できます
ステップ 1.3:テストがあることを確認する

動作が維持されることをテストで検証できる場合に限り、安全にリファクタリングできます。

discover:
  queries:
    - id: test_files
      type: glob
      patterns: ["**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}"]
    - id: source_files
      type: glob
      patterns: ["src/**/*.{ts,tsx}"]
  verbosity: files_only

重要なルール:

  • テストがない場合は、リファクタリングの前にテストを作成します
  • テストは、動作が維持されることを確認するための安全網として機能します
  • リファクタリングの前後にテストを実行します
フェーズ 2:関数/コンポーネントを抽出する

大きな関数やコンポーネントは、テストや保守が困難です。再利用可能な部分を抽出します。

ステップ 2.1:抽出できる箇所を特定する

繰り返されているロジックや、1つの処理を行うコードブロックを探します。

precision_read:
  files:
    - path: "src/components/UserProfile.tsx"
      extract: symbols
  verbosity: standard

抽出候補:

  • 特定のタスクを1つ行うコードブロック
  • 複数箇所で繰り返されているロジック
  • 50行を超える関数
  • 複数の責務を持つ React コンポーネント
ステップ 2.2:関数を抽出する

変更前(検証ロジックが重複):

// In user-routes.ts
export async function POST(request: Request) {
  const body = await request.json();
  if (!body.email || !body.email.includes('@')) {
    return Response.json({ error: 'Invalid email' }, { status: 400 });
  }
  // ... rest of logic
}

// In profile-routes.ts
export async function PUT(request: Request) {
  const body = await request.json();
  if (!body.email || !body.email.includes('@')) {
    return Response.json({ error: 'Invalid email' }, { status: 400 });
  }
  // ... rest of logic
}

変更後(検証ロジックを抽出):

// lib/validation.ts
import { z } from 'zod';

export const emailSchema = z.string().email();

export function validateEmail(email: unknown): { valid: true; email: string } | { valid: false; error: string } {
  const result = emailSchema.safeParse(email);
  if (!result.success) {
    return { valid: false, error: 'Invalid email format' };
  }
  return { valid: true, email: result.data };
}

// user-routes.ts
import { validateEmail } from '@/lib/validation';

export async function POST(request: Request) {
  const body = await request.json();
  const emailResult = validateEmail(body.email);
  if (!emailResult.valid) {
    return Response.json({ error: emailResult.error }, { status: 400 });
  }
  // ... rest of logic using emailResult.email
}

precision_edit を使用して抽出します:

precision_edit:
  operations:
    - action: replace
      path: "src/api/user-routes.ts"
      old_text: |
        const body = await request.json();
        if (!body.email || !body.email.includes('@')) {
          return Response.json({ error: 'Invalid email' }, { status: 400 });
        }
      new_text: |
        const body = await request.json();
        const emailResult = validateEmail(body.email);
        if (!emailResult.valid) {
          return Response.json({ error: emailResult.error }, { status: 400 });
        }
  verbosity: minimal
ステップ 2.3:React コンポーネントを抽出する

変更前(複数の責務を持つ大きなコンポーネント):

function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);
  const [posts, setPosts] = useState<Post[]>([]);
  const [followers, setFollowers] = useState<User[]>([]);

  useEffect(() => {
    fetch(`/api/users/${userId}`).then(r => r.json()).then(setUser);
    fetch(`/api/users/${userId}/posts`).then(r => r.json()).then(setPosts);
    fetch(`/api/users/${userId}/followers`).then(r => r.json()).then(setFollowers);
  }, [userId]);

  if (!user) return <div>Loading...</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.bio}</p>
      
      <div>
        <h2>Posts</h2>
        {posts.map(post => (
          <div key={post.id}>
            <h3>{post.title}</h3>
            <p>{post.content}</p>
          </div>
        ))}
      </div>
      
      <div>
        <h2>Followers</h2>
        {followers.map(follower => (
          <div key={follower.id}>
            <img src={follower.avatar} alt="" />
            <span>{follower.name}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

変更後(コンポーネントとフックを抽出):

// hooks/useUser.ts
export function useUser(userId: string) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      });
  }, [userId]);

  return { user, loading };
}

// components/UserPosts.tsx
export function UserPosts({ userId }: { userId: string }) {
  const [posts, setPosts] = useState<Post[]>([]);

  useEffect(() => {
    fetch(`/api/users/${userId}/posts`).then(r => r.json()).then(setPosts);
  }, [userId]);

  return (
    <div>
      <h2>Posts</h2>
      {posts.map(post => (
        <PostCard key={post.id} post={post} />
      ))}
    </div>
  );
}

// components/UserFollowers.tsx
export function UserFollowers({ userId }: { userId: string }) {
  const [followers, setFollowers] = useState<User[]>([]);

  useEffect(() => {
    fetch(`/api/users/${userId}/followers`).then(r => r.json()).then(setFollowers);
  }, [userId]);

  return (
    <div>
      <h2>Followers</h2>
      {followers.map(follower => (
        <FollowerCard key={follower.id} user={follower} />
      ))}
    </div>
  );
}

// components/UserProfile.tsx
function UserProfile({ userId }: { userId: string }) {
  const { user, loading } = useUser(userId);

  if (loading || !user) return <div>Loading...</div>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.bio}</p>
      <UserPosts userId={userId} />
      <UserFollowers userId={userId} />
    </div>
  );
}

メリット:

  • 各コンポーネントの責務が1つになります
  • 個別にテストしやすくなります
  • アプリケーション全体で再利用できます
  • 個々の部分をメモ化できるため、パフォーマンスが向上します
フェーズ 3:名前を変更して再編成する

不適切な命名やファイル構成は、開発を遅らせます。

ステップ 3.1:命名を改善する

変数、関数、ファイルの名前を、内容が分かるものに変更します。

悪い命名:

function getData(id: string) { ... }  // Too generic
const x = getUserById(userId);  // Unclear abbreviation
let flag = true;  // What does this flag represent?

良い命名:

function getUserProfile(userId: string) { ... }  // Specific, clear intent
const userProfile = getUserById(userId);  // Descriptive
let isEmailVerified = true;  // Boolean naming convention

名前の変更には、replace_all を指定した precision_edit を使用します:

precision_edit:
  operations:
    - action: replace
      path: "src/lib/user.ts"
      old_text: "function getData"
      new_text: "function getUserProfile"
      replace_all: true
  verbosity: minimal
ステップ 3.2:ファイル構成を再編成する

機能ベースまたはレイヤーベースの構成を使用して、関連ファイルをまとめます。

変更前(フラットな構成):

src/
  user-routes.ts
  user-service.ts
  user-repository.ts
  post-routes.ts
  post-service.ts
  post-repository.ts

変更後(機能ベースの構成):

src/
  features/
    users/
      api/
        routes.ts
      services/
        user-service.ts
      repositories/
        user-repository.ts
      types/
        user.types.ts
      index.ts  # Barrel export
    posts/
      api/
        routes.ts
      services/
        post-service.ts
      repositories/
        post-repository.ts
      types/
        post.types.ts
      index.ts

メリット:

  • 関連するコードを同じ場所にまとめられます
  • ファイルを見つけやすくなります
  • 機能の境界が明確になります
  • バレルエクスポートによってインポートが簡潔になります

バレルエクスポートの作成には precision_write を使用します:

precision_write:
  files:
    - path: "src/features/users/index.ts"
      content: |
        export * from './types/user.types';
        export * from './services/user-service';
        export * from './repositories/user-repository';
  verbosity: minimal
フェーズ 4:条件分岐を簡素化する

ネストした条件分岐や複雑なブール論理は、エラーの原因になります。

ステップ 4.1:ガード節を使用する

変更前(ネストした条件分岐):

function processOrder(order: Order) {
  if (order.status === 'pending') {
    if (order.items.length > 0) {
      if (order.paymentConfirmed) {
        // Process order
        return processPayment(order);
      } else {
        throw new Error('Payment not confirmed');
      }
    } else {
      throw new Error('Order has no items');
    }
  } else {
    throw new Error('Order is not pending');
  }
}

変更後(ガード節):

function processOrder(order: Order) {
  if (order.status !== 'pending') {
    throw new Error('Order is not pending');
  }

  if (order.items.length === 0) {
    throw new Error('Order has no items');
  }

  if (!order.paymentConfirmed) {
    throw new Error('Payment not confirmed');
  }

  return processPayment(order);
}

メリット:

  • 構造がフラットになり、読みやすくなります
  • エラー条件を最初に確認できます
  • 正常系の処理を最後に配置できます
Step 4.2: 条件分岐を名前付き関数に抽出する

変更前(複雑なブール論理):

if (user.role === 'admin' || (user.role === 'moderator' && user.permissions.includes('delete')) || user.id === post.authorId) {
  deletePost(post.id);
}

変更後(名前付き関数):

function canDeletePost(user: User, post: Post): boolean {
  if (user.role === 'admin') return true;
  if (user.role === 'moderator' && user.permissions.includes('delete')) return true;
  if (user.id === post.authorId) return true;
  return false;
}

if (canDeletePost(user, post)) {
  deletePost(post.id);
}

メリット:

  • コード自体が説明になる
  • ロジックを再利用できる
  • テストが容易になる
Step 4.3: 複雑な条件分岐にストラテジーパターンを使う

変更前(長い switch 文):

function calculateShipping(order: Order): number {
  switch (order.shippingMethod) {
    case 'standard':
      return order.weight * 0.5;
    case 'express':
      return order.weight * 1.5 + 10;
    case 'overnight':
      return order.weight * 3 + 25;
    case 'international':
      return order.weight * 5 + 50;
    default:
      throw new Error('Unknown shipping method');
  }
}

変更後(ストラテジーパターン):

interface ShippingStrategy {
  calculate(weight: number): number;
}

class StandardShipping implements ShippingStrategy {
  calculate(weight: number): number {
    return weight * 0.5;
  }
}

class ExpressShipping implements ShippingStrategy {
  calculate(weight: number): number {
    return weight * 1.5 + 10;
  }
}

class OvernightShipping implements ShippingStrategy {
  calculate(weight: number): number {
    return weight * 3 + 25;
  }
}

class InternationalShipping implements ShippingStrategy {
  calculate(weight: number): number {
    return weight * 5 + 50;
  }
}

const shippingStrategies: Record<string, ShippingStrategy> = {
  standard: new StandardShipping(),
  express: new ExpressShipping(),
  overnight: new OvernightShipping(),
  international: new InternationalShipping(),
};

function calculateShipping(order: Order): number {
  const strategy = shippingStrategies[order.shippingMethod];
  if (!strategy) {
    throw new Error('Unknown shipping method');
  }
  return strategy.calculate(order.weight);
}

メリット:

  • 開放/閉鎖原則(新しいストラテジーを簡単に追加できます)
  • 各ストラテジーを個別にテストできます
  • 長い switch 文をなくせます
Phase 5: 型の改善

厳密な型付けにより、コンパイル時にバグを検出できます。

Step 5.1: any 型をなくす

すべての any の使用箇所を検索します:

precision_grep:
  queries:
    - id: any_usage
      pattern: ":\\s*any(\\s|;|,|\\))"
      glob: "**/*.{ts,tsx}"
  output:
    format: locations
  verbosity: standard

変更前(安全ではない):

function processData(data: any) {
  return data.value.toUpperCase();  // Runtime error if value is not a string
}

変更後(型安全):

interface DataWithValue {
  value: string;
}

function processData(data: DataWithValue): string {
  return data.value.toUpperCase();
}
Step 5.2: 判別可能なユニオン型を使う

変更前(弱い型付け):

interface ApiResponse {
  success: boolean;
  data?: User;
  error?: string;
}

function handleResponse(response: ApiResponse) {
  if (response.success) {
    console.log(response.data.name);  // TypeScript can't guarantee data exists
  }
}

変更後(判別可能なユニオン型):

type ApiResponse =
  | { success: true; data: User }
  | { success: false; error: string };

function handleResponse(response: ApiResponse) {
  if (response.success) {
    console.log(response.data.name);  // TypeScript knows data exists here
  } else {
    console.error(response.error);  // TypeScript knows error exists here
  }
}

メリット:

  • 網羅的な型チェック
  • 不可能な状態を表現できなくなります
  • IntelliSense の精度が向上します
Step 5.3: ジェネリック制約を追加する

変更前(汎用的すぎる):

function getProperty<T>(obj: T, key: string) {
  return obj[key];  // Type error: no index signature
}

変更後(適切な制約):

function getProperty<T extends Record<string, unknown>, K extends keyof T>(
  obj: T,
  key: K
): T[K] {
  return obj[key];
}

const user = { name: 'Alice', age: 30 };
const name = getProperty(user, 'name');  // Type is string
const age = getProperty(user, 'age');    // Type is number

メリット:

  • 型推論が正しく機能します
  • 無効なキーへのアクセスをコンパイル時に検出できます
Phase 6: 依存関係の逆転

実装ではなく抽象に依存させて、コードを疎結合にします。

Step 6.1: インターフェースを抽出する

変更前(密結合):

import { PrismaClient } from '@prisma/client';

class UserService {
  private prisma = new PrismaClient();

  async getUser(id: string) {
    return this.prisma.user.findUnique({ where: { id } });
  }
}

変更後(依存性注入):

interface UserRepository {
  findById(id: string): Promise<User | null>;
  create(data: CreateUserInput): Promise<User>;
  update(id: string, data: UpdateUserInput): Promise<User>;
}

class PrismaUserRepository implements UserRepository {
  constructor(private prisma: PrismaClient) {}

  async findById(id: string): Promise<User | null> {
    return this.prisma.user.findUnique({ where: { id } });
  }

  async create(data: CreateUserInput): Promise<User> {
    return this.prisma.user.create({ data });
  }

  async update(id: string, data: UpdateUserInput): Promise<User> {
    return this.prisma.user.update({ where: { id }, data });
  }
}

class UserService {
  constructor(private userRepo: UserRepository) {}

  async getUser(id: string) {
    return this.userRepo.findById(id);
  }
}

// Usage
const prisma = new PrismaClient();
const userRepo = new PrismaUserRepository(prisma);
const userService = new UserService(userRepo);

メリット:

  • テストで簡単にモックできます(偽のリポジトリーを注入します)
  • UserService を変更せずに、Prisma を別の ORM に置き換えられます
  • 関心事が明確に分離されます
Step 6.2: ファクトリーパターンを使う

変更前(テストが困難):

function sendEmail(to: string, subject: string, body: string) {
  const client = new SendGridClient(process.env.SENDGRID_API_KEY!);  // BAD: Non-null assertion bypasses runtime validation
  client.send({ to, subject, body });
}

変更後(ファクトリーの注入):

interface EmailClient {
  send(email: { to: string; subject: string; body: string }): Promise<void>;
}

class SendGridEmailClient implements EmailClient {
  constructor(private apiKey: string) {}

  async send(email: { to: string; subject: string; body: string }) {
    // SendGrid implementation
  }
}

class MockEmailClient implements EmailClient {
  async send(email: { to: string; subject: string; body: string }) {
    console.log('Mock email sent:', email);
  }
}

function createEmailClient(): EmailClient {
  if (process.env.NODE_ENV === 'test') {
    return new MockEmailClient();
  }
  const apiKey = process.env.SENDGRID_API_KEY;
  if (!apiKey) {
    throw new Error('SENDGRID_API_KEY environment variable is required');
  }
  return new SendGridEmailClient(apiKey);
}

function sendEmail(client: EmailClient, to: string, subject: string, body: string) {
  return client.send({ to, subject, body });
}

メリット:

  • テストで実際のメールを送信しません
  • メールプロバイダーを簡単に切り替えられます
  • 環境に応じた動作が可能です
Phase 7: データベースのリファクタリング

データベースのスキーマとクエリは、慎重にリファクタリングする必要があります。

Step 7.1: パフォーマンス向上のためにインデックスを追加する

既存のスキーマを確認します:

precision_read:
  files:
    - path: "prisma/schema.prisma"
      extract: content
  verbosity: standard

変更前(インデックスなし):

model Post {
  id        String   @id @default(cuid())
  title     String
  published Boolean  @default(false)
  authorId  String
  createdAt DateTime @default(now())
}

変更後(インデックスあり):

model Post {
  id        String   @id @default(cuid())
  title     String
  published Boolean  @default(false)
  authorId  String
  createdAt DateTime @default(now())

  @@index([authorId])
  @@index([published, createdAt])
}

インデックスを追加するタイミング:

  • 外部キー(authorId、userId)
  • WHERE 句で使うフィールド(published、status)
  • ORDER BY で使うフィールド(createdAt、updatedAt)
  • 複数列のクエリには複合インデックス
Step 7.2: データベーススキーマを正規化する

変更前(非正規化):

model Order {
  id            String @id
  customerName  String
  customerEmail String
  customerPhone String
}

変更後(正規化):

model Customer {
  id     String @id
  name   String
  email  String @unique
  phone  String
  orders Order[]
}

model Order {
  id         String   @id
  customerId String
  customer   Customer @relation(fields: [customerId], references: [id])

  @@index([customerId])
}

メリット:

  • データの重複がなくなります
  • 顧客情報を一か所で更新できます
  • 参照整合性が保たれます
Step 7.3: クエリを最適化する

変更前(N+1 クエリ):

const posts = await prisma.post.findMany();
for (const post of posts) {
  const author = await prisma.user.findUnique({ where: { id: post.authorId } });
  console.log(`${post.title} by ${author.name}`);  // Note: Use structured logger in production
}

変更後(先読み):

const posts = await prisma.post.findMany({
  include: {
    author: true,
  },
});
for (const post of posts) {
  console.log(`${post.title} by ${post.author.name}`);  // Note: Use structured logger in production
}

discover を使って N+1 パターンを見つけます:

discover:
  queries:
    - id: n_plus_one
      type: grep
      pattern: "(for|forEach|map).*await.*(prisma|db|query|find)"
      glob: "**/*.{ts,tsx,js,jsx}"
  verbosity: locations
Phase 8: 検証とテスト

テストで検証して初めて、リファクタリングは安全になります。

Step 8.1: リファクタリング前にテストを実行する

ベースラインを確立します。着手前にテストがすべて成功する必要があります。

precision_exec:
  commands:
    - cmd: "npm run test"
  verbosity: standard

テストに失敗した場合は、まず修正してください。

Step 8.2: 段階的にリファクタリングする

小さな変更を加え、各ステップの後に検証します。

ワークフロー:

  1. テストを実行する(ベースラインを確立)
  2. リファクタリングを1つ実施する
  3. もう一度テストを実行する
  4. テストに成功したら、コミットして続行する
  5. テストに失敗したら、変更を元に戻して原因を調査する

precision_exec を使って検証します:

precision_exec:
  commands:
    - cmd: "npm run typecheck"
    - cmd: "npm run lint"
    - cmd: "npm run test"
  verbosity: standard
Step 8.3: リファクタリングしたコードにテストを追加する

カバレッジが低下した場合は、テストを追加します。

precision_exec:
  commands:
    - cmd: "npm run test -- --coverage"
  verbosity: standard

カバレッジを確認します:

  • 関数のカバレッジは 80% 以上にします
  • 重要な処理経路のカバレッジは 100% にします
  • エッジケースには個別のテストを用意します
Phase 9: ドキュメントを更新する

リファクタリングしたコードに合わせて、ドキュメントも更新する必要があります。

Step 9.1: JSDoc コメントを更新する

変更前(古い内容):

/**
 * Gets user data from the database
 */
function getData(id: string) { ... }  // Function was renamed

変更後(最新の内容):

/**
 * Retrieves a user profile by ID including related posts and followers
 * @param userId - The unique identifier for the user
 * @returns User profile with posts and followers, or null if not found
 */
function getUserProfile(userId: string): Promise<UserProfile | null> { ... }
Step 9.2: README とアーキテクチャドキュメントを更新する

ファイル構成を変更した場合は、ドキュメントを更新します。

更新例:

  • README のファイルツリーを更新する
  • インポートの例を更新する
  • アーキテクチャ図を更新する
  • 貢献ガイドラインを更新する
フェーズ 10:自動検証

検証スクリプトを実行し、リファクタリングの品質を確認します。

./scripts/validate-refactoring.sh /path/to/project

スクリプトで検証する項目:

  • 変更前後でテストが合格すること
  • コードの重複が減っていること
  • 型安全性が向上していること(新たな any 型がないこと)
  • 関数がサイズ制限内であること
  • インポートサイクルが解消されていること
  • テストカバレッジが維持または向上していること

よく使われるリファクタリングパターン

詳しい変更前後の例については、references/refactoring-patterns.md を参照してください。

クイックリファレンス:

メソッドの抽出

対象: 関数が長すぎる、または複数の処理を行っている場合
対処: 論理的なブロックを別々の関数に抽出します

変数/関数の名前変更

対象: 名前が不明瞭、または誤解を招く場合
対処: 説明的で、規約に沿った名前に変更します

条件分岐をポリモーフィズムに置き換える

対象: 型に基づく switch 文がある場合
対処: ストラテジーパターンまたはクラス階層を使用します

パラメーターオブジェクトの導入

対象: 関数のパラメーターが多すぎる場合
対処: 関連するパラメーターをオブジェクトにまとめます

マジックナンバーの置き換え

対象: 文脈を示さない数値がハードコードされている場合
対処: 名前付き定数に切り出します

重複した条件分岐の断片を統合する

対象: すべての分岐に同じコードがある場合
対処: 共通コードを条件分岐の外に移動します

リファクタリング用の高精度ツール

Discover ツール

コードベース全体からリファクタリング候補を見つけます。

例:重複の検出

discover:
  queries:
    - id: validation_patterns
      type: grep
      pattern: "if.*!.*email.*includes"
      glob: "**/*.{ts,tsx}"
    - id: large_functions
      type: symbols
      query: "function"
  verbosity: locations
Precision Edit

安全かつアトミックなリファクタリング編集を実行します。

例:関数の抽出

precision_edit:
  operations:
    - action: replace
      path: "src/api/user.ts"
      old_text: |
        if (!email || !email.includes('@')) {
          return { error: 'Invalid email' };
        }
      new_text: |
        const emailValidation = validateEmail(email);
        if (!emailValidation.valid) {
          return { error: emailValidation.error };
        }
  verbosity: minimal
Precision Exec

リファクタリングによって問題が発生していないことを検証します。

precision_exec:
  commands:
    - cmd: "npm run typecheck"
    - cmd: "npm run lint -- --fix"
    - cmd: "npm run test"
  verbosity: standard

検証スクリプト

scripts/validate-refactoring.sh を使用して、リファクタリングの品質を検証します。

./scripts/validate-refactoring.sh /path/to/project

スクリプトで確認する項目:

  • 変更前後でテストが合格すること
  • コードの重複に関する指標
  • 型安全性(新たな any 型がないこと)
  • 関数のサイズ制限
  • インポートサイクル
  • テストカバレッジが維持されていること

クイックリファレンス

リファクタリングのチェックリスト

リファクタリングの前:

  • テストが存在し、合格する
  • 現在のコード構造を理解する
  • リファクタリングの目標を特定する(抽出、名前変更、再編成)
  • すべての使用箇所を把握する

リファクタリング中:

  • 小さく段階的に変更する
  • 変更のたびにテストを実行する
  • コミットの目的を絞り、アトミックにする
  • インポートと参照を更新する

リファクタリングの後:

  • すべてのテストに合格する
  • 型チェックに合格する
  • Lint に合格する
  • テストカバレッジが維持または向上している
  • ドキュメントが更新されている
  • 検証スクリプトを実行する
リファクタリングを行うタイミング

リファクタリングに適したタイミング:

  • 新機能を追加する前(先に整理する)
  • バグを修正するとき(修正と同時に構造を改善する)
  • コードレビュー中(改善を提案する)
  • コードの問題点に気づいたとき(重複、複雑さ)

リファクタリングに適さないタイミング:

  • 緊急の問題をデバッグしている最中(先に修正し、後でリファクタリングする)
  • テストがないとき(安全ではなく、動作を壊すおそれがある)
  • 締め切り間近のとき(バグを持ち込むリスクがある)
  • 要件が急速に変化しているとき
リファクタリングの安全ルール
  1. テストは必須です - テストなしでリファクタリングしないでください
  2. 一度に一つの変更を行います - リファクタリングと機能開発を混在させないでください
  3. 頻繁に検証します - 変更のたびにテストを実行してください
  4. アトミックにコミットします - 各コミットは完全に動作する状態にしてください
  5. 動作を維持します - リファクタリングで機能を変更しないでください

高度な手法

高精度ツールを使った自動リファクタリング

discover でパターンを見つけてから、precision_edit で修正します。

# Step 1: Find all console.log statements
discover:
  queries:
    - id: console_logs
      type: grep
      pattern: "console\\.log\\("
      glob: "src/**/*.{ts,tsx}"
  verbosity: locations

# Step 2: Replace with proper logger
precision_edit:
  operations:
    - action: replace
      path: "src/file1.ts"
      old_text: "console.log("
      new_text: "logger.info("
      replace_all: true
  verbosity: minimal
並列リファクタリング

バッチ操作を使用して、複数のファイルを同時にリファクタリングします。

precision_edit:
  operations:
    - action: replace
      path: "src/api/user.ts"
      old_text: "getData"
      new_text: "getUserProfile"
      replace_all: true
    - action: replace
      path: "src/api/post.ts"
      old_text: "getData"
      new_text: "getPostDetails"
      replace_all: true
  verbosity: minimal
リファクタリング指標

改善状況を継続的に追跡します。

リファクタリング前に測定:

precision_exec:
  commands:
    - cmd: "find src -not -path '*/node_modules/*' -not -path '*/dist/*' -name '*.ts' -exec wc -l {} + | tail -1"
    - cmd: "grep -r --include='*.ts' --exclude-dir=node_modules --exclude-dir=dist --exclude-dir=.git --exclude-dir=.next -- 'any' src | wc -l"
  verbosity: standard

リファクタリング後に測定して比較:

  • コード行数(重複を削除した場合は減少するはずです)
  • any の使用数(減少するはずです)
  • ファイル数(大きなファイルを分割した場合は増える可能性があります)
  • テストカバレッジ(維持または向上するはずです)

他のスキルとの連携

  • リファクタリングの必要性を特定するには、code-review を使用します
  • 型を改善するパターンには、type-safety スキルを使用します
  • エラー処理ロジックをリファクタリングする際は、error-handling スキルを使用します
  • 十分なテストカバレッジを確保するには、testing-strategy を使用します
  • UI コンポーネントをリファクタリングする際は、component-architecture を使用します
  • データベースクエリをリファクタリングする際は、database-layer を使用します

リソース

  • references/refactoring-patterns.md - 一般的なリファクタリングパターンとその例
  • scripts/validate-refactoring.sh - リファクタリングを自動検証するスクリプト
  • Martin Fowler のリファクタリングカタログ - https://refactoring.com/catalog/
  • Robert C. Martin 著『Clean Code』
  • Martin Fowler 著『Refactoring: Improving the Design of Existing Code』

コメント