副作用のある処理を分離する練習

入門 | 12分 で読める | 2026.06.18

公式ドキュメント

今回やること

この記事では、計算処理と副作用のある処理を分けます。

DB保存やメール送信を計算ロジックから分けると、テストと変更がしやすくなります。

前提条件

  • 関数とasync/awaitが読める
  • DB保存やメール送信が外部への影響を持つことを理解している
  • テストしやすいコードを書きたい

Step 1: 副作用が混ざったコードを見る

async function checkout(cart: Cart) {
 let total = 0;

 for (const item of cart.items) {
 total += item.price * item.quantity;
 }

 await orderRepository.save({
 userId: cart.userId,
 total,
 });

 await mailer.send(cart.userEmail, `Total: ${total}`);

 return total;
}

合計計算、DB保存、メール送信が1つの関数に入っています。

Step 2: 計算処理を切り出す

function calculateCartTotal(items: CartItem[]) {
 return items.reduce((total, item) => {
 return total + item.price * item.quantity;
 }, 0);
}

この関数は外部へ影響しません。同じ入力なら同じ結果になります。

Step 3: 副作用を外側に残す

async function checkout(cart: Cart) {
 const total = calculateCartTotal(cart.items);

 await orderRepository.save({
 userId: cart.userId,
 total,
 });

 await mailer.send(cart.userEmail, `Total: ${total}`);

 return total;
}

合計計算は分離できました。DB保存とメール送信は、外部I/Oとして外側の流れに残しています。

Step 4: テストしやすさを確認する

calculateCartTotal は、DBやメールを用意しなくてもテストできます。

const total = calculateCartTotal([
 { price: 1000, quantity: 2 },
 { price: 500, quantity: 1 },
]);

console.log(total); // 2500

計算ロジックの確認が簡単になりました。

Step 5: 副作用の名前を明確にする

副作用を含む関数には、動作が分かる名前を付けます。

名前分かること
saveOrderDBなどへ保存する
sendReceiptMailメールを送る
writeAuditLogログを書く
fetchUser外部から取得する

名前で副作用が分かると、呼び出し側が注意できます。

よくあるエラー

状況原因対応
テストで本物のDBが必要計算と保存が混ざっている計算関数を切り出す
同じ入力で結果が変わる外部状態に依存している依存を引数にする
メールが誤送信されるテストと本番処理が分離されていないモックやFakeを使う
関数名から副作用が分からない名前が曖昧save / send / fetch を入れる

まとめ

副作用のある処理は必要ですが、計算ロジックと混ざるとテストしにくくなります。まず純粋な計算を切り出し、外部I/Oは分かる名前で閉じ込めましょう。

参考リソース

← 一覧に戻る
PR
PR
PR
PR