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;
}

なぜ間違っているのか:この関数はバリデーション、計算、データベース操作、Eメール、在庫を処理する。テストにはすべての依存関係をモックする必要があります。税金のロジックやバリデーションを変更するには、この関数全体を修正する必要があります。

✅ 準拠:

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;
}

なぜこれが重要なのか: 各機能には明確な責任がある。 validateOrder() そして 計算合計 はモックなしで独立してテストできる。 createOrder() はデータベースロジックを分離します。Eメールや在庫操作は注文作成をブロックせず、失敗は別々に処理されます。

結論

新しいフィールドの追加、新しいエンドポイントの追加、オプションのパラメータの追加など、追加的な変更を通じてAPIを進化させる。やむを得ず変更を加える場合は、APIのバージョニングを使って新旧のバージョンを同時に実行する。古いフィールドを削除する前に、明確なタイムラインと移行ガイドで非推奨にする。

よくある質問

ご質問は?

長い関数を分解するには?

機能内の明確な責任を特定する。検証を別の関数に抽出する。計算を純粋な関数にまとめる。I/O操作(データベース、APIコール)を独自の関数に移す。抽出された各関数は、明確な単一の目的と説明的な名前を持つべきである。

小さな関数はオーバーヘッドを増やし、パフォーマンスを低下させるのではないか?

最近のコンパイラーやインタープリターは小さな関数をインライン化し、呼び出しのオーバーヘッドをなくしている。保守性の利点に比べれば、性能への影響はごくわずかだ。最適化する前にプロファイルを作成する。実際のボトルネックを特定すれば、読みやすいコードは後で最適化しやすくなる。

シーケンシャルなステップの多い関数についてはどうですか?

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

抽出後に多くのパラメータを必要とする関数をどのように扱えばよいですか?

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

たとえ一度しか呼び出されなくても、関数を抽出すべきでしょうか?

そうだ。よく名前が付けられた抽出関数は、コメントよりもコード・ブロックが何を行うかを文書化します。単発の抽出は、複雑なロジックを明確にしたり、親関数の入れ子レベルを減らしたりする場合に価値があります。

まずは無料で体験

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

クレジットカードは不要。