Aikido

関数を簡潔に保つ方法:保守性の高いコードの書き方

可読性

ルール

維持 関数 簡潔に
長い 関数  難しい 理解 理解し、 テストが 維持 保守が

対応言語: 45以上

はじめに

数百行にわたる関数は複数の責任を混在させ、すべての行を読まずにその関数が何をするのかを理解することを困難にします。長い関数は通常、検証、ビジネスロジック、データ変換、エラー処理など、複数の懸念事項をすべて一箇所で処理します。これは単一責任の原則に違反し、既存の動作を壊すことなくテスト、デバッグ、変更が困難なコードを生み出します。

なぜ重要なのか

コードの保守性:長い関数は、開発者がその動作を理解するためにより多くのコンテキストを頭の中に保持することを要求します。すべてのロジックが絡み合っているため、一部を変更すると別の部分を壊すリスクがあります。意図しない副作用を予測することが困難なため、バグ修正はリスクを伴います。

テストの複雑さ: 200行の関数をテストすることは、1つのテストですべての可能なコードパスをカバーすることを意味し、複雑なセットアップと多数のテストケースを必要とします。より小さな関数は、集中的な単体テストで独立してテストできるため、テストスイートをより高速で信頼性の高いものにできます。

コード例

❌ 非準拠:

async function processOrder(orderData) {
    if (!orderData.items?.length) throw new Error('Items required');
    if (!orderData.customer?.email) throw new Error('Email required');
    const subtotal = orderData.items.reduce((sum, item) => 
        sum + (item.price * item.quantity), 0);
    const tax = subtotal * 0.08;
    const total = subtotal + tax + (subtotal > 50 ? 0 : 9.99);
    const order = await db.orders.create({
        customerId: orderData.customer.id,
        total: total
    });
    await emailService.send(orderData.customer.email, `Order #${order.id}`);
    await inventory.reserve(orderData.items);
    return order;
}

なぜこれが問題なのか: この関数は、バリデーション、計算、データベース操作、メール、在庫管理を処理します。テストには、すべての依存関係のモックが必要です。税ロジックやバリデーションに変更を加える場合、この関数全体を修正する必要があります。

✅ 準拠済み:

function validateOrder(orderData) {
    if (!orderData.items?.length) throw new Error('Items required');
    if (!orderData.customer?.email) throw new Error('Email required');
}

function calculateTotal(items) {
    const subtotal = items.reduce((sum, item) => 
        sum + (item.price * item.quantity), 0);
    return subtotal + (subtotal * 0.08) + (subtotal > 50 ? 0 : 9.99);
}

async function createOrder(customerId, total) {
    return await db.orders.create({ customerId, total });
}

async function processOrder(orderData) {
    validateOrder(orderData);
    const total = calculateTotal(orderData.items);
    const order = await createOrder(orderData.customer.id, total);
    
    // Non-critical operations in background
    emailService.send(orderData.customer.email, `Order #${order.id}`).catch(console.error);
    
    return order;
}

これが重要である理由: 各関数は1つの明確な責任を持ちます。 validateOrder() そして calculateTotal() モックなしで独立してテストできます。 createOrder() データベースロジックを分離します。メールおよび在庫操作が注文作成を妨げず、障害は個別に処理されます。

まとめ

追加的な変更を通じてAPIを進化させます。新しいフィールドの追加、新しいエンドポイントの追加、オプションパラメーターの追加などです。破壊的変更が避けられない場合は、APIバージョニングを使用して古いバージョンと新しいバージョンを同時に実行します。古いフィールドは、明確なタイムラインと移行ガイドを提示した上で非推奨とし、その後削除します。

よくある質問

ご質問がありますか?

長い関数を分割するにはどうすればよいですか?

関数内の明確な責務を特定します。検証を別の関数に抽出し、計算を純粋関数に分離します。I/O操作(データベース、API呼び出し)をそれぞれの関数に移動させます。抽出された各関数は、明確で単一の目的を持ち、分かりやすい名前であるべきです。

小さな関数はオーバーヘッドを追加し、パフォーマンスを損ないませんか?

最新のコンパイラやインタプリタは、小さな関数をインライン化することで呼び出しオーバーヘッドを排除します。パフォーマンスへの影響は、保守性のメリットと比較するとごくわずかです。最適化の前にプロファイリングを行ってください。可読性の高いコードは、実際のボトルネックを特定した際に後で最適化しやすくなります。

多数の連続的な処理を持つ関数についてはどうですか?

連続したステップは、より小さな機能に分割できるワークフローを示唆しています。各ステップにヘルパー関数を作成し、コーディネーター関数から順に呼び出します。これにより、ワークフローが読みやすくなり、各ステップを独立してテストできるようになります。

抽出後に多くのパラメータを必要とする関数をどのように処理しますか?

長いパラメータリストではなく、関連するパラメータを含むオブジェクトを渡します。または、抽出された関数が共有状態を保持するクラスのメソッドであるべきかどうかを検討してください。関数が6つ以上のパラメータを必要とする場合、それは抽象化の不足またはデータ構造の欠如を示している可能性があります。

一度しか呼び出されない場合でも関数を抽出すべきですか?

はい、抽出によって可読性が向上する場合です。適切に命名された抽出関数は、コメントよりもコードブロックの機能を明確に示します。複雑なロジックを明確にしたり、親関数のネストレベルを減らしたりする場合に、一度限りの抽出が有効です。

今すぐ、安全な環境へ。

コード、クラウド、ランタイムを1つの中央システムでセキュアに。
脆弱性を迅速に発見し、自動的に修正。

クレジットカードは不要です。 | スキャン結果は32秒で表示されます。