Design
Context¶
IndexMerger と IndexEditor は index.json のグループ・メンバー配列を操作するサービスクラスである。現在、配列の find() メソッドでルックアップしているため O(n) の計算量となっている。
現在のコード構造:
- IndexMerger.merge(): 配列をシャローコピー後、groups.find() と members.find() で既存エントリを検索
- IndexEditor.updateGroupName(): 配列をシャローコピー後、groups.find() で対象グループを検索
いずれも不変性を維持するため、先に map() で配列のシャローコピーを生成し、その後 find() で検索している。
Goals / Non-Goals¶
Goals:
- find() による O(n) ルックアップを Map.get() による O(1) ルックアップに置換する
- 外部インターフェース(引数・戻り値)を一切変更しない
- 既存テストが全件パスすることでロジックの同一性を保証する
Non-Goals:
- 配列コピーの不変性パターン自体の変更(map() によるシャローコピーは維持)
- IndexMerger / IndexEditor 以外のサービスのリファクタリング
- ベンチマークの追加やパフォーマンス計測の導入
Decisions¶
1. シャローコピー配列と Map の併用パターン¶
決定: map() でシャローコピーした配列と、id → オブジェクト参照 の Map を同時に構築する
理由: 現在の実装では map() で配列をコピーした後に find() で検索している。Map はコピー済みオブジェクトへの参照を持つため、Map 経由で取得したオブジェクトを直接変更すれば、配列側にも反映される。これにより:
- 不変性パターン(入力配列は変更しない)を維持できる
- 追加のメモリコストは Map のエントリ分のみ(オブジェクト自体は共有)
- find() をそのまま Map.get() に置き換えるだけで済む
代替案: 配列をやめて Map のみで管理する → 戻り値の構造が変わるため不採用
2. Map の構築タイミング¶
決定: 配列の map() コピーと同時に Map を構築する(reduce または forEach で1パスで処理)
理由: 配列コピーと Map 構築を別々のループで行うと 2パスになる。1パスで両方を構築することで効率的に処理できる。
3. IndexEditor での Map 適用¶
決定: updateGroupName() でも同じパターンを適用する
理由: 単一の find() 呼び出しに対して Map を構築するのはオーバーヘッドに見えるが、コードの一貫性を重視する。また、将来的にメソッドが増えた場合にも同じパターンを踏襲できる。
Risks / Trade-offs¶
- [少量データでの微小なオーバーヘッド] → グループ・メンバー数が少ない場合、Map 構築のコストが
find()より大きくなる可能性がある。ただし実用上は無視できるレベルであり、データ増加時のスケーラビリティを優先する - [コードの複雑性の微増] → Map 構築のコードが追加されるが、パターンが明確なため可読性への影響は最小限