概要
副作用(side effect)は、プログラムにおける見えない変化の根源である。
特にJavaScriptのような柔軟な言語では、副作用は便利さと同時に予測困難な挙動の原因ともなる。
本稿では、純粋関数の原則、副作用の識別と分離、制御構造としての活用戦略に焦点を当てる。
テスト容易性や拡張性を高める上で、副作用をどのように扱うべきか、その設計視点を明確にする。
1. 純粋関数 vs 非純粋関数
// 純粋関数
function add(a, b) {
return a + b;
}
// 非純粋関数(副作用あり)
function addAndLog(a, b) {
const result = a + b;
console.log(result); // ← 副作用
return result;
}
- ✅ 純粋関数は入力に対して出力が常に一定
- ❌ 非純粋関数は外部とのやり取りを含み、テストが困難
2. よくある副作用の例
種類 | 具体例 |
---|---|
I/O |
console.log() , alert() , fetch() など |
DOM操作 |
document.querySelector() , appendChild()
|
状態変更 | グローバル変数の書き換え |
時間参照 |
Date.now() , setTimeout() など |
ランダム性 | Math.random() |
3. 副作用の分離設計
// ✗ ロジックと副作用が混在
function updateAndRender(data) {
const transformed = transform(data);
document.body.innerHTML = render(transformed);
}
// ✓ 分離された構造
function update(data) {
return transform(data);
}
function applyToDOM(html) {
document.body.innerHTML = html;
}
- ✅ ロジックと出力(副作用)を明確に切り分ける
- ✅ 副作用部分は常に境界として設計し、外側でまとめて扱う
4. 副作用制御のためのレイヤー分離
// pure.ts
export function calculateTax(price) {
return price * 0.1;
}
// effect.ts
import { calculateTax } from './pure.js';
const tax = calculateTax(1000);
console.log(`Tax is: ${tax}`);
- ✅ ロジックと副作用が別ファイル → テスト可能・再利用可能
- ✅ 副作用のある処理は最小限のゲートウェイとして設計
5. フレームワークにおける副作用制御(React例)
useEffect(() => {
document.title = 'Loaded';
}, []);
- ✅ Reactでは副作用は
useEffect
に隔離 - ✅ 「純粋なレンダリング関数 + 副作用専用の構造」を徹底している設計思想
設計判断フロー
① この関数は常に同じ出力を返すか? → 純粋関数として設計可能
② 外部と通信しているか? → 副作用として分離・制御
③ テスト可能な構造か? → 外部依存を渡す or 別モジュールに切り出す
④ UI更新とロジックが混在していないか? → DOM操作を外に出す
⑤ 時間やランダム性を使っていないか? → モック・依存注入に切り替える
よくあるミスと対策
❌ console.log()
を開発時から本番まで放置
→ ✅ 副作用は環境ごとに制御(ロガーインタフェースの抽象化)
❌ DOM操作が関数内にベタ書きされ、再利用不能に
→ ✅ DOM操作は呼び出し元が責任を持ち、関数は値だけ返す
❌ グローバル状態を直接書き換えてテスト不能
→ ✅ 状態は関数の戻り値として受け渡す or ストア経由で制御
結語
副作用を完全に排除する必要はない。
むしろそれを構造として認識し、制御下に置くことこそが設計の本質である。
- 純粋関数をベースに、拡張しやすくテスト可能な設計を構築
- 副作用は「悪」ではなく、「境界」であり、「責務」である
- アーキテクチャとは、副作用をどこまで許容するかの戦略選択でもある
JavaScriptにおける副作用設計とは、
“振る舞いと環境との接点を明確にし、予測可能性を守るための設計制御である。”