Interactive

関数

関数の書き方を明確に指定することで、ClaudeCodeは予測可能でテストしやすい関数を生成できます。弊社では、副作用を最小限に抑えた実装を基本方針としています。

弊社の推奨ルール

  1. 純粋関数を優先 - 外部状態に依存せず、副作用を排除
  2. Early Returnで複雑度削減 - ネストを避けて読みやすく
  3. 引数は3個まで - それ以上はオブジェクトで渡す
  4. 関数は20行以内 - 複雑な処理は分割必須
  5. 必ず型定義を行う - anyは使用禁止

ClaudeCodeでの利用

関数設計の依頼

「このif文のネストをEarly Returnで整理して」
→ ガード節を使った平坦な構造に変換
「この関数を純粋関数に変更して」
→ 副作用を除去した実装に変換
「このバリデーション処理を純粋関数で実装して」
→ 外部状態に依存しない関数として生成
「複数の引数をオブジェクトにまとめて可読性を向上させて」
→ 引数オブジェクトパターンに変換

副作用のある関数ではなく純粋関数を使う

外部状態に依存せず、同じ入力に対して常に同じ出力を返す純粋関数を優先します。副作用を最小限に抑えることで、テストしやすく予測可能なコードになります。

// ❌ 推奨しない:副作用のある関数
let counter = 0; // グローバル状態

function processData(data: string): string {
  counter++; // 外部状態を変更(副作用)
  console.log(`Processing: ${data}`); // 出力も副作用
  return `${data}_${counter}`;
}

// テスト時に外部状態に依存するため不安定
console.log(processData('test')); // "test_1"
console.log(processData('test')); // "test_2" (同じ入力で異なる出力)

このコードは外部状態に依存するため、テストが不安定で、結果が予測できません。

// ✅ 推奨:純粋関数
function processData(data: string, id: number): string {
  // 外部状態に依存しない
  // 同じ入力に対して常に同じ出力
  return `${data}_${id}`;
}

// 使用側で状態管理
function processWithLogging(data: string, counter: number): { result: string; newCounter: number } {
  const result = processData(data, counter + 1); // 純粋関数を呼び出し
  console.log(`Processing: ${data}`); // 副作用は使用側で管理
  return { result, newCounter: counter + 1 };
}

// テスト時に予測可能
console.log(processData('test', 1)); // "test_1" (常に同じ結果)
console.log(processData('test', 1)); // "test_1" (同じ入力で同じ出力)

このパターンにより、関数の動作が予測可能になり、単体テストが容易になります。

深いネストではなくEarly Returnを使う

複雑な条件分岐を読みやすくするため、異常系を先に処理します。深いネストは可読性を低下させ、バグの温床となります。

// ❌ 推奨しない:深いネスト
function processUser(user: User): UserResult {
  if (user) {
    if (user.isActive) {
      if (user.hasPermission) {
        const result = performOperation(user);
        return { success: true, data: result };
      } else {
        return { success: false, error: 'Permission denied' };
      }
    } else {
      return { success: false, error: 'User inactive' };
    }
  } else {
    return { success: false, error: 'User not found' };
  }
}

このコードは3層のネストがあり、どこで何をチェックしているかわかりにくいです。

// ✅ 推奨:Early Returnで平坦化
function processUser(user: User): UserResult {
  // 異常系を先に処理(ガード節)
  if (!user) {
    return { success: false, error: 'User not found' };
  }
  
  if (!user.isActive) {
    return { success: false, error: 'User inactive' };
  }
  
  if (!user.hasPermission) {
    return { success: false, error: 'Permission denied' };
  }
  
  // 正常系の処理(ネストなし)
  const result = performOperation(user);
  return { success: true, data: result };
}

このパターンにより、各条件が明確に分離され、メインロジックが最後に集約されます。

多数の引数ではなくオブジェクトを使う

引数が4個以上になると可読性が著しく低下するため、オブジェクトパターンで解決します。

// ❌ 避ける:引数が多すぎる
function createUser(
  name: string,
  email: string, 
  age: number,
  address: string,
  phone: string
) {
  // 処理
}

// ✅ 推奨:オブジェクトにまとめる
type CreateUserProps = {
  name: string;
  email: string;
  age: number;
  address?: string;  // オプショナル
  phone?: string;    // オプショナル
}

function createUser(props: CreateUserProps) {
  // props.name のように直接参照
  // 処理
}

関数名を明確にする

関数の責務を名前で表現するため、動詞+目的語の形式を使用します。単一責任の原則に従い、何をする関数かが名前から理解できるようにします。

// ❌ 責務が不明確
function handle(data: any) {
  // 何をするのか不明
}

// ✅ 責務が明確
function validateUserInput(input: UserInput): ValidationResult {
  // ユーザー入力を検証することが明確
}

function transformDataToDto(data: RawData): UserDto {
  // データをDTOに変換することが明確
}

function calculateTotalPrice(items: Item[]): number {
  // 合計金額を計算することが明確
}

このパターンにより、関数の目的が明確になり、コードの可読性が向上します。

よくある問題

型推論が効かない

ジェネリクス関数で型推論が正しく動作しない問題です。ジェネリクスの制約を明確にし、条件型を活用することで解決できます。

// ❌ 型推論が効かない
function processData<T>(data: T) {
  return data.toString(); // エラー:TにtoStringがあるか不明
}

// ✅ 制約で型を絞り込み
function processData<T extends { toString(): string }>(data: T) {
  return data.toString(); // OK:制約により型安全
}

関数内で非同期処理が混在する

同期処理と非同期処理が混在して、予期しない動作になる問題です。非同期処理がある場合は関数全体をasyncにして統一します。

複雑な型パラメータで推論が困難

複数のジェネリクス型パラメータで推論が複雑になる問題です。型パラメータを段階的に推論するか、明示的に型を指定します。