概要
グローバル状態は、コードの見通しを曇らせる最大の要因の一つである。
便利だが制御しにくく、依存関係を不透明にし、テストや再利用性を著しく下げる。
しかし、完全に排除することは現実的ではない。
だからこそ重要なのは、**グローバル状態を「管理する設計」**である。
本稿では、グローバル状態の構造的取り扱い、スコープ制御、依存性注入(DI)、状態の局所化による戦略的な状態設計について掘り下げる。
1. グローバル状態が持つリスク
let currentUser = null;
function login(name) {
currentUser = { name };
}
function greet() {
return `Hello, ${currentUser?.name ?? 'Guest'}`;
}
- ❌ 状態がどこで変更されたか分かりづらい
- ❌ テスト・拡張が難しく、複数のモジュールに影響
- ❌ モジュールの結合度が高まり、依存の方向が不明瞭に
2. スコープを絞った状態管理(クロージャ戦略)
function createUserStore() {
let user = null;
return {
login(name) {
user = { name };
},
getUser() {
return user;
}
};
}
const userStore = createUserStore();
- ✅ 外部からの直接操作を防ぎ、インターフェース越しにアクセス
- ✅ 閉包によるスコープ制御で安全性とテスト容易性が向上
3. 依存性注入によるグローバル回避
function greet(userStore) {
const user = userStore.getUser();
return `Hello, ${user?.name ?? 'Guest'}`;
}
- ✅ 依存関係を引数として受け渡す
- ✅ モジュールの独立性が保たれ、モックの注入も容易
4. モジュールスコープでの状態保持
// userStore.js
let user = null;
export function login(name) {
user = { name };
}
export function getUser() {
return user;
}
- ✅ 共有状態は モジュールの中で閉じ込める
- ✅ 名前付きエクスポートで明示的に責務を表現
5. UIレイヤにおける状態管理(React/Vue等)
const [user, setUser] = useState(null);
- ✅ グローバルでなく、ローカルな状態を必要な単位で管理
- ✅ グローバルが必要な場合でも Context API / Store構成で管理レイヤを定義
設計判断フロー
① その状態は複数箇所からアクセスされるか? → ストアとして分離
② 外部から直接操作できてしまうか? → クロージャまたはモジュールで制御
③ テストで状態を差し替えたいか? → DI設計に変更
④ 状態が不透明に変更されていないか? → getter/setter型のインターフェース導入
⑤ 将来的に非同期 or 永続化される可能性は? → ストア層で切り出しておく
よくあるミスと対策
❌ 直接 window.X = ...
でグローバル注入し、デバッグが困難に
→ ✅ モジュール or ファクトリを用いて明示的に設計
❌ 状態の変更箇所が散乱してトラブルシュート困難
→ ✅ setX
, getX
のインターフェース越しに管理統一
❌ グローバル状態を変更する関数が無制限に増殖
→ ✅ 状態のスコープと依存性を制御する責務分割を導入
結語
グローバル状態は「悪」ではない。
だが、それが制御されないまま野放しになることが危険なのだ。
- スコープを限定し、インターフェース越しに制御する
- 状態の変更経路を明示し、依存性の方向を単純化
- 必要なときだけ、必要な形で、安全にアクセスする
JavaScriptにおける状態設計とは、
“変更と依存を予測可能にし、構造の中で責務を分離することで、保守性と拡張性を保証する戦略である。”