構造的失敗を先に作る勇気でDBマイグレーションを書き直した話
MicroWin アプリ Day 4 PR1 を作っていた夜のことだった。user_version 配列で DB マイグレーションを駆動するという設計は、恥ずかしながら 3 回目で諦めた過去がある。SQLite 互換で PRAGMA user_version を上げる順番で適用する方式は明快だが、各 step の冪等性をどう担保するかで毎回詰まっていた。
3 度目の挫折の夜、ふと「失敗を先に作る」コードを書いた。SQLite の PRAGMA user_version が 0 から始まっている状態で、step 1 だけ実行して検証するテストを書く。2 を実行して検証するテストを書く。0 に戻して全部一気に実行するテストを書く。一気に実行する途中でわざと接続を切るテストを書く。書きながら震えた。「こんな書き方、職業エンジニアとして恥ずかしい」と思った。書き終えてコーヒーを淹れた。
翌朝、恥ずかしいコードが動いた。破損した中間状態(user_version=1 で 2 を実行)も、3 度目の step(=もう 1 に戻して全部実行)を呼べば recovery できる。冪等ではない遷移を冪等な「3 度実行で必ず収束する」プロトコルに変換した。これは TDD の中でも「失敗してから書く」red-green-refactor の red を強調する流派で、恥ずかしかったが機能した。
Day 4 末の最終ビルドで 111 テスト緑・coverage 98%・tsc=0 を確認した時、あの夜の恥ずかしさが誇りに変わった。失敗を先に作るのは逃げではなく、設計の強度を測る試験だ。
<details> <summary>技術解説: user_version 配列駆動 DB マイグレーションの設計詳細</summary>
設計の核
PRAGMA user_versionで DB の状態を一意に表現- migrations 配列を
Migrations = [step1_sql, step2_sql, ...]として定義 - 実行:
current = get_user_version()→for i in range(current, len(Migrations)): apply(Migrations[i])→set_user_version(i + 1) - 冪等性: 同じ
currentで何度実行してもMigrations[current:]を再適用するだけで収束
失敗モードへの対処
- 途中で接続切断 → 起動時に
current取得から再実行 = 冪等な recovery - step N が部分失敗 →
current < Nの状態で残る → step N から再実行 - アプリダウングレード →
user_versionが新しい値を保持 = 「未来 step を適用しない」= 自動 downgrade 防止
テスト戦略(Day 4 実装)
- TDD red-green-refactor の red を強調
- 失敗ケースを「先に書く」: 接続切断 / 部分失敗 / 不正 step 順
- 各失敗ケースで「3 度実行で必ず収束する」ことを assertion
- coverage 98% = 失敗ケースが網羅されている指標
評価
- 11 commits(5 file 541 行追加 + 4 ファイル計 31 テスト追加)
- 111 テスト全緑・tsc=0
- Day 5+ 着手 = 行 49/行 120 の dead branch 削除判断(A2/B 採用)
- 残 = bootstrap path の transaction 包囲(high severity 残務)
関連 SSOT
- 01_DECISIONS/microwin-app/2026-10-17_MicroWin-SprintA-Day4実装-user_version-migrations-framework-PR1.md(実装 SSOT 記録)
- MLR r1-r4 4 ラウンド計 129 件指摘・critical 11 件即修正
公開判定
- 機密: なし(実装コード・テスト戦略のみ)
- 誇張: なし(実測値のみ・coverage 98%・test count 111)
- 一般化: 実用的(DB マイグレーション設計の参考事例として)
- 公開判定: publish </details>