関数
関数の書き方を明確に指定することで、ClaudeCodeは予測可能でテストしやすい関数を生成できます。弊社では、副作用を最小限に抑えた実装を基本方針としています。
弊社の推奨ルール
- 純粋関数を優先 - 外部状態に依存せず、副作用を排除
- Early Returnで複雑度削減 - ネストを避けて読みやすく
- 引数は3個まで - それ以上はオブジェクトで渡す
- 関数は20行以内 - 複雑な処理は分割必須
- 必ず型定義を行う - 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にして統一します。
複雑な型パラメータで推論が困難
複数のジェネリクス型パラメータで推論が複雑になる問題です。型パラメータを段階的に推論するか、明示的に型を指定します。