副作用とは、関数が戻り値を返す以外に、外の状態へ影響を与えることです。
難しく聞こえますが、Webアプリでは毎日のように出てきます。
一言でいうと
副作用は、関数の外側に変化を起こす処理です。
DBへ保存する、APIを呼ぶ、ファイルに書く、ログを出す、グローバル変数を変える、画面状態を変える。これらは副作用です。
副作用の例
let total = 0;
function addPrice(price: number) {
total += price;
return total;
}
この関数は、外側の total を変更しています。同じ引数でも、呼ぶタイミングによって結果が変わります。
副作用が少ない例
function calculateTotal(prices: number[]) {
return prices.reduce((sum, price) => sum + price, 0);
}
この関数は、外の状態を変えません。同じ入力なら同じ結果になります。
副作用が悪いわけではない
副作用は必要です。DBへ保存しないWebアプリはほとんどありません。メール送信、API通信、ログ出力も必要です。
問題は、副作用がどこにあるか分からないことです。
| 副作用 | 分かりにくいと起きる問題 |
|---|---|
| DB保存 | テストで本物のDBを書き換える |
| API呼び出し | 実行するたび結果が変わる |
| ログ出力 | 個人情報を出してしまう |
| 状態変更 | どこで値が変わったか追えない |
分ける考え方
計算と副作用を分けると、テストしやすくなります。
function calculateTotal(items: Item[]) {
return items.reduce((sum, item) => sum + item.price, 0);
}
async function saveOrder(items: Item[]) {
const total = calculateTotal(items);
await orderRepository.save({ items, total });
}
calculateTotal は簡単にテストできます。DB保存は別の場所に閉じ込めています。
初学者が見るポイント
- 関数の外側の変数を変更していないか
- DBやAPIを呼んでいないか
- ファイルやログを書いていないか
- 同じ入力で同じ結果になるか
- テスト時に外部サービスが必要になっていないか
副作用がある関数には、名前で分かるようにするのも大切です。saveOrder、sendMail、writeLog のように動作を明示します。
まとめ
副作用は、関数の外側に変化を起こす処理です。
副作用をなくすのではなく、計算処理と分けて、どこで外部へ影響するか分かるようにしましょう。