コンテンツにスキップ

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 構築のコードが追加されるが、パターンが明確なため可読性への影響は最小限