この記事で扱うこと
すでに作ってあった機能を 別のプロジェクトへ移したいが、コードをそのままコピーはしたくない という状況で学んだ二つのことを整理する。機能は「画面に見える行だけを描く(仮想化)無限スクロールリスト + 戻る/再読み込みしても位置が復元される」ものだった。
一つ目は どう移すか — 実装コードではなく「この機能は見た目にどう振る舞うか」を契約として書くクリーンルーム方式。二つ目はその過程で改めて確認した 無限スクロールの核心的なデータ問題 — リストを offset で切るのか keyset(カーソル)で切るのか。特定の会社・製品の話はなく、どこでも通用する一般概念だけを扱う。
今日扱ったこと
| 作業 | 何をしたかったか | やったこと | 結果 |
|---|---|---|---|
| クリーンルーム契約 | 機能をコードコピーなしで別スタックへ移管 | 実装コードを外し「見た目の振る舞い + 失敗保証」だけを契約として文書化 | 移す側がライブラリ選択に縛られない |
| keyset ページネーション | 無限スクロールが挿入/削除でも壊れないように | offset の問題を整理し双方向カーソル契約で置き換え | 重複・欠落を除去、深いページも一定した性能 |
1. コードではなく「振る舞い」を移すクリーンルーム方式
どんな状況だったか。 よく動く機能を別のプロジェクトへ移そうとしているが、そのプロジェクトは自分たちと別のライブラリを使うかもしれない。コードをそのままコピペすると、自分たちのツール選択(特定のデータフェッチライブラリ、特定の仮想化ライブラリ)を強制することになる。
核心概念。 クリーンルーム(clean-room)移管 は、元のコードを一行もコピーせず、「この機能が見た目に どう振る舞うか」だけを文書として渡し、相手に最初から新しく実装させる方式である。ここで区別が重要で、能力(capability) は「無限にスクロールでき、戻ると位置が復元される」という見た目の振る舞いであり、ツール(tooling) はそれを実現する特定のライブラリである。能力だけを書けば、ツール選択は移す側の自由だ。料理にたとえると「温かい汁物料理」が能力、「この鍋」がツールだ — レシピ(能力)だけを渡し、他人の厨房の道具は持ち込まない。
やったこと。 機能を四層の 振る舞い契約 に分解した: (1) UI/仮想化 — 見える行だけを DOM に置き、行の高さは 測定値 → 保存された高さ → 平均推定 の順で決める、(2) ドメイン/モデル — 双方向のページ配列とメモリ上限、(3) 転送/API — クエリの組み立てと失敗時の throw、(4) 復元 — スナップショットのスキーマと復元判定。各契約項目には「直接確認した / 観察した / 推論した / 不明」のラベルを付け、読む人が 検証済みの事実と推測を区別 できるようにした。実装コードブロックは一行も入れず、代わりに「失敗したら何を保証するか」(失敗シナリオ)と「これはやらない」(non-goal)を明示した。
教訓。 機能移管ドキュメントの核心は、自分たちが書いたコードではなく 見た目の振る舞い + 失敗時の保証 である。根拠ごとに確信ラベルを付ければ、移す側がリスクを管理できる。
2. 無限スクロールの安定性はページネーションから来る
どんな状況だったか。 リストをどんどん下に(または上に)継ぎ足していくが、その間に新しい記事が追加されたり削除されたりすると、リストがずれる。さっき見た最後の記事が再び出てきたり(重複)、あった記事を飛ばしたり(欠落)する。
核心概念。 原因はたいてい ページネーション方式 にある。offset ページネーション は「11 番目から 10 件」のように飛ばす件数でページを要求する(LIMIT 10 OFFSET 10)。後ろに行くほど前の行をすべて数えるので遅くなり(O(offset))、リストがリアルタイムに変わると項目がずれて重複・欠落する。keyset(cursor)ページネーション は「最後に見た記事のソートキー以降をくれ」のように 最後に見た値 を座標として使う。インデックスでその地点を直接見つけるので速く(O(log n))、間に挿入・削除があっても「この値の次」は変わらないので安定している。本にたとえると offset はページ番号(誰かが途中にページを挟むとずれる)、keyset はしおり(最後に読んだ文を基準に続きを読む)だ。
やったこと。 無限スクロール API をカーソルベースの双方向契約に整理した。上にスクロールすれば before(より新しい)、下にスクロールすれば after(より古い)カーソルを使い、最初の要求はカーソルなしで始める。不正なカーソル/範囲外の limit は 黙って最初のページにフォールバックせず 400 で即座に失敗 させ、クライアントが位置喪失を知れるようにした。積み上がり続けるページは保持数の上限を設けてメモリを bound した。
教訓。 無限リストの安定性の大部分は offset か keyset かで分かれる。不正な入力は黙って飲み込まず、素早く失敗させるのが、位置喪失を隠さない道だ。