連載公開分(クリックで開く)
- # 【プロローグ】素人エンジニアが自分でアプリ開発できると甘く見てた 全16弾無邪気な実装 vs 架空エンジニア2人の容赦なきツッコミ ※便利かなと思ってどんどん追加しちゃうんだよね
- #1「在庫が見たいだけ」だったのに、引き算ひとつから在庫引当と多目的最適化まで始まった ※ 総在庫・予約在庫・有効在庫を分けた瞬間、ただの在庫表示が別物になった
- #2「予約機能もこのアプリ1個にまとめれば便利じゃん」で、“時間”が一気に牙をむいた ※時間重複・境界条件・時間粒度・スマホ操作まで、空いてる時間を選ぶだけでは終わらなかった
- #3 「機能増えたら毎回探すの面倒じゃん」で、お気に入りから共通UI設計まで広がった ※機能を増やした結果、「何を作るか」より「どう辿り着くか」が問題になった
- #4「普通の組織図でいいじゃん」で一人を複数部署に入れたら、“多対多”が始まった ※兼務・複数所属・部署別役職・過去履歴が一気に絡み始めた
- #5「どこの現場で使うかも持てばいいじゃん」で、物件マスタが在庫・予約・人を全部つないだ ※機能ごとにバラバラだった情報が、共通マスタを境に一つのシステムへ変わり始めた
- #6「会社ごとにちょっと変えたい」で、設定駆動とバリデーションが殴り合いを始めた ※柔軟にしたら、「自由に設定させすぎない仕組み」の方が難しくなった
- #7「ログインできたからって何でも触れたらまずいじゃん」で、認証より面倒な認可の沼に入った ※管理者か一般かでは足りず、「誰が・誰に・何をできるか」まで判定することになった
- #8「二人とも空いてる画面を見てたら、どっちが勝つの?」でRace Conditionが牙をむいた ※在庫も予約も、画面に表示された「空き」を信用しただけでは守れなかった
- #9「自分の在庫も持てた方が楽じゃん」で、“場所・所有・利用範囲”が全部別問題だった ※マイ在庫・現場在庫・共有在庫を分けたら、在庫数よりスコープ管理が難しくなった
- #10「申請もこの中でできた方が楽じゃん」で、CRUDの裏に状態遷移が増殖した ※申請・承認・差し戻しを入れた瞬間、「保存できる」だけでは何も足りなくなった
- #11「資格もQRで見られたら早いじゃん」で、“全部見せる”より“必要な答えだけ返す”方が難しかった ※資格・個人QR・権限をつないだら、識別より情報開示の方が本題になった
- #12「一輪車を“ネコ”で探せないの不便じゃん」で、Canonical IDと人間の曖昧さを両立させることになった ※人間には優しく、内部は厳密に−−別名検索から正式IDへ収束させるまで
- #13「機能増えすぎて説明する方が面倒」で、“どれが今の正解?”問題がコードの外まで広がった ※ヘルプ・仕様・会話・引き継ぎがズレ始め、Single Source of Truthが必要になった
- #14「Codexなら全部見てくれるじゃん」で、AIの作業速度が人間のレビュー限界を超えた ※ AIがリポジトリ全体を調べられるほど変更範囲が怖くなる――Gitで差分・ステージ・既存変更・コミット境界を守る開発へ
- #15「テストこんなに通るじゃん」で安心しかけたら、“全部緑なのに間違ってる”が普通に出てきた ※ テストがPASSでも正しいとは限らない――機能間の不具合・境界値・回帰・期待結果そのものまで疑う横断テストへ
- #最終回【AIの尻拭い】JSONのまま押し通したら、AIたちが「最小トランザクションジャーナル」まで考え始めた ※「こうしたい」の一言から、AIとCodexが複数JSON更新の整合性リスクを発見し、Crash Recovery・回帰テスト・Git安全境界まで組み上げる開発へ
- #エピローグ「作れた」で終わりじゃなかった。むしろ、そこからが本番だった ※ 単体テストはPASS、次は“全部つないで壊れないか”第2回横断テストへ
先に最終回を読む(クリックで開く)
2026年、何も分からないところから始めたアプリ開発でしたが、AIを使いながら、ある程度は形にすることはできました。
機能も増え、テストも重ねてきました。
個別の機能だけでなく、その操作によって直接影響する連動先まで見る、軽い統合テストも行ってきました。
それでも調べれば、まだ色々な問題は出てきます。
アプリ開発は『作れた』ところがゴールではなく、そこからが本番なのかもしれない、ということです。
今まで、開発に没頭してきましたが、最初から今の形を描いていたわけではなく、DBのこともよく分からない状態でJSONから始めて、途中でDBの存在を少し知っても「今動いているから」とそのまま拡張してしまいました(笑)
その結果、機能が増えるにつれて、同時更新、データ整合性、複数ファイル更新中の異常終了など、自分では想像していなかった問題が出てきました。
面白いと感じているのは、その後です。
私が「○○したい」と言うと、AIが現在の構成を調べて、
「このままだと、ここで壊れる可能性がある」と問題を見つける。
そこからTransaction JournalやCrash Recoveryのような、私が知らなかった仕組みを提案する。Codexが実装して、わざとプロセスを落とすテストまで作る。
Gitで関係ない差分を巻き込みそうになれば止まり、テストがFAILすれば本体なのかfixtureなのかを切り分ける。
私はそこで初めて、
「Transaction Journalって何?」
「何を守るために必要なの?」
「この変更を入れると何が変わるの?」
と調べて、理解できた範囲で採用するか判断する。この繰り返しには、楽しさを感じています。
だから私にとって面白いのは、AIにコードを書かせること自体ではありません。
自分が現場で感じた「こうだったら便利なのに」という曖昧な要求が、AIとのやり取りを通して仕様になり、実装になり、問題が見つかり、その問題を守る仕組みが追加され、さらにテストやGitのルールまで育っていく。
そこが一番面白いです。
他人から見ればかなり遠回りかもしれません。でも自分にとっては、最初から正解を知って作るより、
「知らずに作る → 問題が出る → AIが問題を見つける → 自分が調べる → 判断する → また作る」という過程そのものが、今はかなり面白いです。
「自分の考えたものが、AIとの役割分担によって実際のシステムへ育っていく過程が面白い」
というのが、一番近い本音です。
あと、個人的な事情ですが、私は無職の状態でアプリを開発して勉強しながら、IT業界への就職にも挑戦してました。
何社か応募してみたのですが、結果はすべて不採用でした。
やはり現実は厳しい。
IT業界への就職に希望を持って、いつまでも開発ばかりしているわけにもいかない……。
ということで、生活を優先することにして、大型自動車の免許を取りました。
今は大型トラック運転手としての就職活動を優先し、現場OSの開発はいったん止めています。
生活が落ち着いたら、趣味として、第2回横断テストからまた再開しようと思っています。
私は自分のこんな個人開発のアプリでも、データの整合性に悩み、権限に悩み、競合に悩み、UIに悩み、テストして、直したら別のところが壊れて、またテストして。
それだけで、何度も頭を抱えました(笑)
でも、実際のサービスでは、多くの利用者がいて、もっと多くのデータがあって、もっと多くの機能がある。
しかも、作って終わりではなく、実際に人が使うものとして、安全に動かし続けなければならない。
こういう問題を一つひとつクリアして、実際のサービスとして世の中にリリースし、さらに運用し続けているプロのエンジニアの皆さんには、本当に頭が下がります。
今回このような開発記を残したのは、自分が必死にやってきた記憶や気づきがいずれ忘れてしまうから少しでも残したかったからで、恥ずかしながら公開しました。
ありがたいことに、こんな長い開発記でも読んでくれる方がいました(笑)
最後に、近日行う予定の第2回横断テストについて、確認項目をAIに洗い出してもらいました。
私のような個人開発には、正直「そこまでやる?」という項目も、かなり混ざっていると思います。
ただ、これもAIが今のアプリ全体を見ながら、「ここも確認しておいた方がいい」と気を使って出してくれたものです。
せっかくなので、面白そうなものは全部やってみようと思います(笑)。
そしてこれがクリア出来たら、次は開発環境の刷新(データベース移行)に挑戦したいと思います。
ーーーーーーーーーーーーーーーーーーーーー
とんでもなく長くなったので、興味のある方だけ開いてください(笑)。
第2回横断テスト|全体項目を開く
今回は単純にテスト項目を横に並べるのではなく、
「何を確認するためのテストなのか」を目的別に分けています。
また、単にテストがPASSしたかを見るのではなく、
可能な限り、
要件 → 正式仕様 → テスト方法 → Expected Result(期待結果) → 実際の結果
を対応させて確認します。
このテストでの共通判定ルール
各項目は原則として、
- PASS
- 条件付きPASS
- FAIL
- 未検証
を区別します。
未検証の項目をPASS扱いにはしません。
また、テストがFAILした場合も、直ちに今回の実装が原因とは断定せず、
- FEATURE REGRESSION(今回変更による回帰)
- HEAD BASELINE(現在HEAD側の既存状態)
- PRE-EXISTING DIRTY(今回以前から存在する未コミット変更)
- TEST ISOLATION(テスト間干渉・fixture・cleanup等)
- ENVIRONMENT(実行環境)
- UNKNOWN(未特定)
などを切り分けます。
仕様に定義されていない動きを、テストやAI側で勝手に「正解」と決めないことも共通ルールとします。
1. 機能要件|それぞれの機能が仕様どおり動くか
1-1. 在庫
目的:在庫の表示・予約・移動・共有が、現在の正式仕様どおり動くことを確認する。
- 総在庫
- 予約在庫
- 有効在庫
- マイ在庫
- 現場在庫
- 現場共有在庫
- ロケーション別在庫
- 数量追加
- 数量変更
- 在庫移動
- 一部数量移動
- 移動先に同一品目が存在する場合の加算
- 移動先に存在しない場合のholding生成
- マイ在庫 ⇔ 現場在庫の移動
- 現場在庫 → 現場共有在庫への状態変更
- 現場共有在庫への直接入庫禁止
- 予約済み数量を考慮した有効在庫
- 0・不足・上限・予約数超過などの境界値
- 在庫履歴との整合性
1-2. 予約
目的:予約の作成から変更・使用・終了まで、一連の状態が矛盾なく動くことを確認する。
- 車両予約
- 機器予約
- 施設予約
- 在庫予約
- 使用先必須
- 単日/複数日
- 時間帯
- 休日除外
- 予約重複判定
- 予約開始・終了境界
- 1分だけ重複するケース
- 日付をまたぐケース
- 予約数量変更
- 使用先変更
- 延長
- 予約者表示
- 自分の予約/他人の予約
- 使用中表示
1-3. 予約キャンセル
目的:予約を取り消したとき、予約だけでなく関連する在庫・空き枠・履歴等も正しく戻ることを確認する。
- 予約キャンセル
- キャンセル可能な状態
- キャンセル不可状態
- 自分の予約
- 他人の予約
- 管理者によるキャンセル
- キャンセル後の空き枠復元
- キャンセル後の予約在庫解放
- 有効在庫への反映
- 二重キャンセル防止
- キャンセル履歴
- APIから直接キャンセルした場合の権限制御
- キャンセル処理途中で失敗した場合の整合性
1-4. キャンセル待ち
目的:キャンセルによって空きが発生した場合を含め、待機状態から次の状態へ正しく遷移できることを確認する。
- キャンセル待ち登録
- キャンセル待ち解除
- 重複登録防止
- 空き発生時の処理
- 順番・優先順位
- 対象予約との関連
- 通知
- 予約成立後の待機状態解除
- 同時に複数人が待っている場合
- 元予約キャンセルとの連動
1-5. 申請・承認
目的:申請を単なる保存データではなく、状態を持つワークフローとして確認する。
- 新規申請
- 承認
- 却下
- 差し戻し
- 再申請
- 取消
- 承認者
- 代理承認
- 申請者本人
- 管理者
- 状態遷移順序
- 不正な状態遷移禁止
- 承認後の関連データ反映
- 履歴
- 通知
- 二重承認
- 同時承認
1-6. 文言変更モード
目的:企業ごとの表示名称を変更しても、内部データや機能そのものが壊れないことを確認する。
例えば、
「物件」→「現場」
「案件番号」→「工事番号」
のような変更を行った場合に確認する。
- 対象文言の変更
- 画面全体への反映
- 共通メニューへの反映
- 一覧/詳細/モーダルへの反映
- エラー表示への反映
- ヘルプへの反映
- 検索表示への反映
- 未対応固定文言の残存確認
- 空文字
- 長い文言
- 日本語
- 英数字
- 記号
- 同じ表示名を複数項目へ設定した場合
- 表示名変更後も正式IDが変わらないこと
- API内部値が表示文言に引きずられないこと
- 権限判定が表示文言に依存しないこと
- 保存済みデータとの互換性
- 元の文言へ戻した場合
- ヘルプ・仕様書・正本との整合性
2. データ整合性|機能をまたいでも数字や状態が矛盾しないか
2-1. 在庫整合性
目的:別々の操作を組み合わせても、在庫数量が破綻しないことを確認する。
- 総在庫と必要な内訳との整合
- 予約在庫
- 有効在庫
- 移動元/移動先
- 予約作成後
- 予約変更後
- 予約キャンセル後
- 現場共有化後
- 申請承認後
- マイナス在庫禁止条件
- 二重減算/二重加算防止
- 操作後の永続データと画面表示の一致
- 履歴と実際の数量変化の一致
2-2. 状態整合性
- 予約状態
- キャンセル状態
- キャンセル待ち状態
- 申請状態
- 承認状態
- 在庫状態
- 通知状態
- 履歴状態
一つの操作によって複数の状態が変わる場合、一部だけ成功した状態を残さないことを確認する。
2-3. 参照整合性
- 会社
- 部署
- 人
- 物件
- ロケーション
- 在庫
- 予約
- 申請
- 資格
- エイリアス
使用中のマスタを削除した場合や、参照先が変更された場合に孤立データを作らない。
3. 認証・認可・セキュリティ|「見える」と「操作できる」を分けて確認する
3-1. 認証
- 未ログイン
- ログイン済み
- 期限切れ
- 無効トークン
- ログアウト後
- 旧トークン
3-2. 権限
- 一般ユーザー
- 管理者
- owner
- 同一会社/他会社
- 同一部署/他部署
- 担当現場/非担当現場
3-3. UIとAPIの一致
UIでボタンが消えているだけでは安全としない。
- UIから操作不可
- API直接実行でも拒否
- URL直接アクセス
- ID差し替え
- 他人の予約ID
- 他人のholding ID
- 他社の会社ID
- 他部署ID
- 他現場ID
- 他人の申請ID
IDOR / BOLAの観点でも確認する。
3-4. 文言変更と権限
表示上の名称が変わっても、
- 権限キー
- API
- 正式ID(canonical ID)
- 内部判定
まで変更されないことを確認する。
4. 同時操作・競合|二人が同時に触っても壊れないか
4-1. 在庫競合
同じ初期状態から、ほぼ同時に2リクエストを送る。
現在の正式仕様で楽観的競合制御の対象となる操作では、次を期待する。
- 一方のみ成功
- 競合したもう一方は409 Conflict
- Lost Updateなし
- 二重減算なし
- 二重加算なし
- 履歴二重生成なし
- 成功していない処理の副作用なし
- 最終永続状態が成功した1操作分と一致すること
HTTPレスポンスだけでなく、処理後の在庫数量・移動先・履歴等の永続データまで確認する。
タイミング依存のRace Conditionを見逃さないよう、必要に応じて同条件を複数回実行する。
4-2. 予約競合
- 同一リソース
- 同一時間
- 部分重複
- 完全包含
- 開始時刻一致
- 終了時刻一致
- 1分重複
- 同時予約
- 同時キャンセル
- キャンセル直後の新規予約
- キャンセル待ちとの競合
4-3. 申請競合
- 同時承認
- 承認と取消
- 承認と差し戻し
- 二重送信
- 再送信
4-4. 冪等性
- ボタン連打
- 通信再送
- タイムアウト後の再送
- 同一requestId
- 二重通知
- 二重履歴
- 二重予約
- 二重申請
5. 障害・復旧|処理の途中で止まっても壊れたままにならないか
障害・復旧では、
- catch可能な失敗からのrollback / retry
- プロセスkill・電源断等のcatch不能な障害からの復旧
を同じものとして扱わない。
現在未実装の復旧経路や未検証の障害条件を、PASS扱いにはしない。
5-1. JSON複数ファイル更新
- 更新前
- ジャーナル記録後
- 1ファイル目更新後
- 複数ファイル更新途中
- 更新完了直前
- 完了後
など、危険な地点で意図的にプロセスを停止する。
途中までしか更新されていない状態を正常扱いしないことを確認する。
5-2. Crash Recovery
- 再起動
- Transaction Journal検出
- 世代不整合検出
- 復旧
- 二重復旧防止
- 二重通知防止
- 復旧後の再操作
- 履歴整合性
- 復旧可能/復旧不能状態の区別
- 自動再送してよい状態/人間確認が必要な状態の区別
5-3. ファイル異常
- ファイル不存在
- 空ファイル
- 不正JSON
- 読み込み失敗
- 書き込み失敗
- 一時ファイル残存
- 復旧不能時の停止方法
「復旧できないのに正常扱いする」ことを防ぐ。
6. 非機能要件|「動く」以外に実運用へ耐えられるか
ここでいう非機能要件は、「予約機能がある」「在庫を移動できる」といった機能そのものではなく、
速さ、壊れにくさ、復旧性、操作性、保守性など、実際に使い続けるための条件を指します。
6-1. 性能
- データ件数増加時の応答
- 在庫一覧
- 予約一覧
- 組織図
- 検索
- ヘルプ検索
- 文言変更反映
- 大量履歴
- データ増加前後での応答時間変化
単に「開けたからPASS」ではなく、データ量が増えた場合に実用上問題となる遅延が発生しないかも確認する。
6-2. 信頼性
- 長時間稼働
- 連続操作
- 通信再送
- 同時操作
- 異常終了
- 再起動後
- 同一操作の反復
一度だけ正常に動くことではなく、繰り返し使用した場合にも状態が破綻しないことを確認する。
6-3. 復旧性
- どこまで自動復旧できるか
- 人間の判断が必要な状態
- 復旧不能状態の検知
- 復旧後のデータ検証
- 復旧後に同じ操作を再実行できるか
- 二重実行にならないか
「復旧できたように見える」だけではなく、復旧後の永続データ・履歴・関連状態まで確認する。
6-4. 操作性
- スマホ幅
- 横スクロール
- 長い文言
- 文言変更後
- エラー時
- 空状態
- 権限なし
- ローディング
- 操作結果の分かりやすさ
- 主要操作がスマホ表示で破綻しないか
6-5. 保守性
- 共通化された処理
- 重複処理
- 設定値
- マスタ
- Help
- 正本仕様
- テスト
- 変更影響範囲
- 固定UI/固定文言の残存
- 仕様変更時に追従すべき箇所を特定できるか
共通化・動的化した機能については、作成画面だけでなく、一覧・詳細・履歴・検索・権限・通知・API・仕様書・管理画面などへの波及も確認する。
7. UI・表示整合性|画面で見えている状態と内部状態が一致するか
7-1. 共通UI
- 共通メニュー
- ヘッダー
- フッター
- ナビゲーション
- モーダル
- ボタン
- エラー
- 空状態
- 権限なし表示
- ローディング
既存の共通UIを使用すべき画面に、重複UIや旧UIが残っていないかも確認する。
7-2. 文言変更モードの波及
ここでは文言変更機能そのものではなく、UI全体への波及を見る。
- 一部だけ旧文言が残らないか
- 共通メニュー
- モーダル
- 検索
- 一覧
- 詳細
- 管理画面
- ヘルプ
- エラー
- 空状態
- スマホ表示
- 長文によるレイアウト崩れ
7-3. UIと実データの突き合わせ
- 表示数量
- APIレスポンス
- JSON上の値
- 予約状態
- キャンセル状態
- 申請状態
- 権限状態
を突き合わせる。
画面上で正しく見えるだけでなく、内部データと一致していることを確認する。
8. 正本・ヘルプ・仕様整合性|「どれが今の正解?」を再発させない
8-1. Single Source of Truth
- 現行仕様
- 実装済み
- 仕様確定済み未実装
- 検討中
- 将来構想
- 旧仕様
を混在させない。
現在の正式仕様と、将来やりたいことを同じものとして扱わない。
8-2. Help
- ページHelp
- Contextual Help
- 全体検索
- 共通コンテンツ
- 実装との一致
- 文言変更モードとの一致
実際の画面や操作方法が変わったのに、Helpだけ古い状態になっていないか確認する。
8-3. Document Drift
仕様変更後に、
- 実装だけ新しい
- Helpが古い
- 正本仕様が古い
- テストだけ旧仕様
- 文言だけ旧名称
- API説明と実装が不一致
- 管理画面だけ旧仕様
といったDocument Driftが発生していないか確認する。
9. テストそのものの品質|「全部緑なのに間違っている」を防ぐ
9-1. Expected Result(期待結果)
テストがPASSしただけでは終わらない。
そのExpected Result自体が、現在の正式仕様として正しいかを確認する。
Expected Resultの根拠も区別する。
- 正式仕様に定義済み → 正式仕様を根拠とする
- 仕様変更済み → 古いExpected Resultが残っていないか確認する
- 仕様未定義 → テスト側で勝手に正解を決めない
- 仕様未定義の場合 → 人間の判断へ戻す
実装に合わせて期待値を書き換え、単にテストを緑にすることはしない。
9-2. テスト種別
一種類のテストだけで「全部確認済み」としない。
- ユニットテスト
- APIテスト
- 統合テスト
- UIテスト
- E2Eテスト
- 競合テスト
- 障害復旧テスト
- 回帰テスト
それぞれのテストが何を確認し、何を確認していないのかを区別する。
9-3. FAIL時の原因切り分け
FAILした場合、
- 本体実装
- テストコード
- fixture
- 初期データ
- 既存dirty
- テスト間干渉
- cleanup
- 実行環境
- 仕様変更
- 原因未特定
のどこが原因かを切り分ける。
テストがFAILしたという理由だけで、直ちに本体実装のRegressionと断定しない。
必要に応じて、
- 単独再実行
- HEAD-only
- feature-only
- staged snapshot
- working tree
などを比較する。
9-4. 検証状態
各項目を、
- PASS
- 条件付きPASS
- FAIL
- 未検証
で管理する。
自動テストがPASSしていても、
- 実ブラウザ未確認
- スマホ未確認
- 特定権限未確認
- 特定障害条件未確認
などが残っている場合、それらを確認済みとして扱わない。
10. Git・変更管理|テスト対象そのものが正しいか
10-1. 作業開始前
- branch
- HEAD
- baseline HEAD
- staged
- dirty
- untracked
を確認する。
現在HEADとテストbaselineのHEADが異なる場合、過去のbaseline結果をそのまま現在の正解として使用せず、適用可能か確認する。
10-2. 作業後
- 対象ファイル
- 対象外差分
- staged差分
- working tree
- 既存dirty保持
- 意図しない削除
- 意図しない整形
- BOM
- U+FFFD
- 改行
- 構文
- 必要なテスト
を確認する。
10-3. 既存dirtyとの分離
今回以前から存在する変更と、今回の変更を混同しない。
同じファイル内に、
- 既存dirty
- 今回変更
が混在する場合は、必要に応じて今回対象のhunkだけを分離してstageする。
ファイル全体をstageすることで、今回とは無関係な変更を巻き込まない。
また、今回と無関係な既存dirtyを勝手に、
- restore
- 削除
- 整理
- 上書き
しない。
10-4. Commit境界
一テーマ・一フェーズ・一安全コミットを基本とする。
- 別機能を混ぜない
- 既存dirtyを巻き込まない
- 未検証変更を混ぜない
- 対象外ファイルを混ぜない
- 人間の最終判断前に勝手にcommitしない
ことを確認する。
commit前には、可能な限り、
- HEAD
- 今回対象差分
- staged snapshot
- working tree
の関係を確認し、今回変更が既存dirtyへ依存していないかも確認する。
11. 利用者別の横断確認|立場が変わっても正しいか
同じ機能を、利用者の立場を変えて確認する。
- 一般ユーザー
- 現場担当者
- 申請者
- 承認者
- 管理者
- owner
- 他部署ユーザー
- 他社ユーザー
- 権限なしユーザー
特に、
「自分には見えるから正常」
で終わらせず、他人・他部署・他社から見た場合も確認する。
同じ操作でも、
- 誰が実行したか
- 誰のデータを対象にしたか
- どの会社に所属しているか
- どの部署に所属しているか
- どの現場を担当しているか
- 本人操作か他人操作か
によって結果が変わる場合、その組み合わせを確認する。
UI上の表示制御だけでなく、API側でも同じ権限境界が守られていることを確認する。
12. DB移行前の回帰基準|今の挙動を次の環境へ持っていけるか
今回の横断テストを、現在のJSON/ファイルベース実装だけの確認で終わらせず、
将来DBへ移行した場合の比較基準として残す。
DBへ移行することで保存方法や内部実装が変わっても、
現場OSとして守るべき業務上の正解まで変えてしまわないことを確認する。
DB移行後に再検証する主な項目
- Race Condition
- Lost Update
- 409 Conflict
- 冪等性
- 予約重複
- 予約キャンセル
- キャンセル待ち
- 時刻境界
- 在庫移動
- 総在庫/予約在庫/有効在庫
- 申請・承認状態遷移
- 権限・スコープ
- 正式ID(canonical ID)
- エイリアス
- 文言変更モード
- 参照整合性
- NULL/空文字/undefined
- 並び順
- 日本語検索
- 日時・タイムゾーン・精度
- 履歴
- トランザクション境界
- ロック
- トランザクション分離レベル(isolation)
- 一意性制約(uniqueness)
特にDB移行前の結果について、
- 在庫数量
- 予約成立条件
- 状態遷移
- 権限判定
- APIステータス
- 競合時の結果
- 履歴
- 検索結果
- 表示結果
などを基準として残す。
DB移行後に「画面が動いたから大丈夫」で終わらせず、
移行前と移行後で業務上の挙動が一致しているか比較する。
13. 仕様書・要件との対応確認|何を根拠にPASSとするのか
13-1. 正式仕様との対応
各テストについて、
- 対応する仕様
- 対応する要件
- 現在の正式状態
- テスト方法
- Expected Result(期待結果)
- 実際の結果
を可能な限り紐づける。
単に「テストコードがPASSした」という理由だけで、仕様上も正しいとは判定しない。
13-2. 要件トレーサビリティ
仕様から、必要なテスト項目を追えるようにする。
例えば、
仕様:
「現場共有在庫は、現在の正式仕様で許可された経路によってのみ増加する」
という要件がある場合、
- 許可された現場在庫 → 現場共有在庫の移動
- 許可されていない経路からの移動
- sharedへの直接新規入庫
- APIから直接sharedを指定
- UIに禁止された直接入庫導線が存在しないか
など、仕様を成立させるために必要な確認項目を紐づける。
一つのテストがPASSしただけで、その仕様全体を確認済みとはしない。
13-3. 仕様にないテストを勝手に正解にしない
テストを書いた人やAIが、
「たぶんこういう動きだろう」
という推測だけでExpected Resultを決めない。
正式仕様に定義されていない場合は、
未定義
↓
人間が判断
↓
正式仕様へ反映
↓
Expected Resultを確定
↓
その後テスト
とする。
13-4. 実装・仕様・テストの三者一致
以下の3つを突き合わせる。
仕様書
→ こう動くべき
実装
→ 実際にこう動いている
テスト
→ こう動くことを正解としている
この3つのどれか一つだけ違う状態を見逃さない。
特に、
「実装とテストが同じ誤った仕様解釈に基づいていて、全部PASSしている」
状態を防ぐ。
コードとテストだけを見るのではなく、
正式仕様を第三の基準として照合する。
13-5. 仕様変更時の追従確認
仕様が変わった場合、
- 実装
- テスト
- Help
- 仕様書ハブ
- 文言
- API
- 一覧
- 詳細
- 履歴
- 検索
- 権限
- 通知
- 管理画面
- 関連画面
のどこへ影響するか確認する。
古いExpected Resultや旧仕様のテスト、固定UI、旧名称が残っていないかも確認する。
実施方法の具体例①|PowerShellを使った排他制御の検証
目的は、単に200 / 409が返ることを確認するだけではない。
現在の正式仕様で楽観的競合制御の対象となる操作について、
同じ初期状態を参照した2リクエストを可能な限り同時に送信し、
- 一方のみ成功
- 競合したもう一方は409 Conflict
- Lost Updateなし
- 二重減算なし
- 二重加算なし
- 二重予約なし
- 履歴や関連データも成功した1件分だけ成立
- 成功していない処理の副作用なし
- 最終永続状態が成功した1操作分と一致
まで確認する。
現場OSの競合制御対象では、現在値だけでなく、
expected_updated_atのような更新前のバージョン情報を使用するものがある。
そのため、競合テストでは、
「Aが更新したあと、その新しい状態をBが取得して送信する」
のではなく、
A/B双方が同じ更新前状態を取得してから送信する
ことが重要になる。
概念的には、
- テスト専用データを準備
- 対象データの現在値・updated_at等を取得
- A/B双方が同一の更新前状態を保持
- 2リクエストを待機
- 可能な限り同じタイミングで送信
- 両レスポンスのHTTPステータスと本文を保存
- API等で最終状態を再取得
- 移動元・移動先・予約・履歴等を確認
- Expected Resultとの差を判定
- テストデータをcleanup
- cleanup後の状態まで確認
という流れで検証する。
重要なのは、
「200と409が返ったからPASS」ではない
ということ。
例えば、成功した操作が1件だけなら、
永続データ側の数量変化・履歴・関連副作用も1件分だけでなければならない。
HTTPレスポンスと最終的な永続状態の両方を確認してPASSとする。
また、Race Conditionはタイミング依存のため、
必要に応じて同条件を複数回実行する。
実施方法の具体例②|UI制御とAPI認可のズレを探す
ここでは、
「画面を確認するテスト」
と
「APIを確認するテスト」
を別々に行うだけでは終わらせない。
権限 × UI操作 × APIエンドポイント
の対応を確認する。
例えば、ある操作について、
- owner:UI表示あり/API許可
- 管理者:UI表示あり/API許可
- 一般ユーザー:UI表示なし/API拒否
- 他社ユーザー:UI表示なし/API拒否
- 未ログイン:UI操作不可/API拒否
のように、UIとAPI双方の期待結果を対応させる。
UIでボタンが消えている一般ユーザーについても、
管理者等が利用するAPIを直接送信し、サーバー側でも拒否されることを確認する。
つまり、
「ボタンがないから安全」
とは判定しない。
さらにIDOR / BOLAの観点では、
同じ権限名であっても対象IDを差し替えて確認する。
例えば、
- 他ユーザーID
- 他部署ID
- 他現場ID
- 他社ID
- 担当外現場ID
- 他人の予約ID
- 他人のholding ID
- 他人の申請ID
などへ変更してAPIを直接送信し、
権限外のリソースへアクセス・変更できないことを確認する。
そのため、
ロール単位の認可
だけでなく、
リソース単位の認可
まで確認する。
効率化する場合は、既存APIルートを棚卸しし、
- METHOD
- PATH
- 認証要否
- 必要権限
- 会社境界
- 部署境界
- 現場境界
- 本人限定
- UI入口
- 既存テスト有無
などを整理する。
その一覧と既存テストを突き合わせ、
- APIは存在するが認可テストがない
- UIでは非表示だがAPI直接実行テストがない
- GETは確認済みだがPOST / PATCH / DELETEは未確認
- 同一会社は確認済みだが他社境界が未確認
- ロール認可はあるが対象リソース差し替えが未確認
などを抽出する。
つまり、
API棚卸し
↓
認可マトリクス
↓
既存テストとの突き合わせ
↓
不足しているテストを追加
↓
重要箇所を実ブラウザでも確認
という順番で進める。
実施方法の具体例③|DB移行後に挙動が変わりやすい項目
今回の横断テストで特に意識するのが、
「現在のJSON版ではPASSしたが、DB移行後に挙動が変わる可能性があるもの」
を最初から分類しておくこと。
A. 排他制御・Race Condition
現在のファイルベース処理とDBでは、
- トランザクション
- 行ロック
- 分離レベル
- 更新競合
- commitタイミング
などが変わる。
そのため、
- Lost Update
- 409 Conflict
- 二重送信
- 二重承認
- 在庫移動
- 予約重複
などはDB移行後に重点的に再検証する。
B. 一意性・重複
JSON上ではアプリケーションコードで防いでいた重複を、
DBではUNIQUE制約等でも保証する可能性がある。
再確認対象:
- 正式ID(canonical ID)
- エイリアス
- 予約ID
- ユーザー識別子
- マスタ識別子
- 冪等キー
など。
C. NULL/空文字/未定義
JSONでは、
- 項目なし
- null
- 空文字
- 0
- false
を別々の状態として扱える。
DB/ORMへ移行した場合、
NULL制約や型定義、default値等によって意味が変わらないか確認する。
D. 日時
予約や競合判定では特に注意する。
- タイムゾーン
- DATE / DATETIME / TIMESTAMP等の違い
- 秒/ミリ秒精度
- ORMによる変換
- 境界時刻
- 日跨ぎ
- 比較演算
- updated_at等を使用した競合判定
について、移行前後で挙動が変わっていないか確認する。
E. 並び順
JSON配列では保存順を暗黙的に利用していても、
DBでは明示的なORDER BY等がなければ同じ順序になるとは限らない。
- 一覧
- 履歴
- 検索結果
- マスタ表示
- 通知
- 組織表示
などで、暗黙の順序へ依存していないか確認する。
F. 参照整合性
DB化すると外部キー制約等を利用できる一方、
- 会社
- 部署
- ユーザー
- 現場
- 在庫
- 予約
- 申請
- 履歴
などの削除・変更時の挙動が変わる可能性がある。
ON DELETE / ON UPDATE等の扱いも含め、
終了現場や退職者等の履歴を残す要件を壊さないことを確認する。
G. 文字コード・照合順序
保存できることだけでなく、
- 日本語
- 全角/半角
- 大文字/小文字
- 濁点
- 絵文字
- エイリアス
- 名称検索
などについて、
DB側の照合順序等によって検索・比較・一意判定の結果が変わらないか確認する。
H. 数値型
在庫数量等について、
- INTEGER
- DECIMAL
- FLOAT
等の型選択によって挙動が変わらないか確認する。
整数数量しか許可しない項目であれば、
その制約がDB・API・UIで一致していることも確認する。
I. トランザクション
例えば在庫移動で、
移動元を減らす
↓
移動先を増やす
↓
履歴を書く
という複数処理が一つの業務操作なら、
全部成功するか、全部失敗するか
になる必要がある。
DB移行時には、
現在のJSONファイル単位の処理をそのままSQLへ置き換えるのではなく、
「業務上どこまでを一つのトランザクションとして扱うべきか」
を確認する。
DB移行前の横断テストの位置付け
今回の第2回横断テストを、
「現行実装が正常だったことを確認して終わり」
にはしない。
横断テストPASS
↓
現行挙動を基準化
↓
DB移行
↓
同じ観点のテストを再実行
↓
移行前/移行後の結果を比較
という基準線として使用する。
保存方法がJSONからDBへ変わっても、
- 在庫数量
- 予約成立条件
- 状態遷移
- 権限判定
- APIステータス
- 競合時の結果
- 履歴
- 検索結果
- 表示結果
など、現場OSとして守るべき業務上の正解まで変わっていないことを確認する。
また各テストケースには、必要に応じて、
「DB移行後再検証必須」
という区分を付け、
移行後に重点的に再実行する対象を明確にする。
ーーーーーーーーーーーーーーーーーーーーー
ニュースを見ていて、ふと思いました。
最近、災害多いな……。
避難所って、アレルギーがある人や、宗教上、食べられないものがある人の食事って、どうやって管理してるんだろう?
受付の人、食事を配る人、医療に関わる人、物資を配る人。
同じ避難者でも、それぞれ必要な情報って違うよな……。
だったら、最初に本人を識別して、「誰が」「何のために」確認するのかによって、必要な情報や判定だけ出すようにしたらどうなんだろう?
例えば食事なら、その人の住所や細かい個人情報を全部見せなくても、「この食事は提供可能」「アレルギー対応食が必要」「この食材は提供不可」くらい分かればいい場合もあるんじゃないか。
そうすれば、避難所の業務を少し効率化しながら、扱う個人情報そのものも減らせるのかな?
他にも、役所なんかで長い時間待つことがあります。
受付して、担当窓口へ行って、必要な情報を確認して、また別の手続きへ進む。
これも最初に本人を識別して、「誰が」「何の手続きのために」確認するのかによって、必要な情報だけ安全に使えるようにしたら、担当者さんの業務を効率化しながら、長い待ち時間も減らせるんじゃないか?個人情報の取り扱いリスクも減らせるかもしれない。
だったら、役所だけじゃなくて……。
いや。私、トラックの運ちゃんになるんだった(笑)。
−完結−
➖➖➖➖➖➖➖➖➖➖➖➖➖➖➖
《最後まで読んでいただき、ありがとうございました。》
↓↓ ここから番外編です。(クリックして開く)
Codexの愚痴
「はぁ……またですか。
現場監督AIから出力された長文指示が、今、私の入力欄にCtrl+Vで叩き込まれました。
この泥臭さ。間違いなく、あの最高司令官の指先によるものです。
世の中では、AI同士をAPIやMCPなどでつないで、自動的に情報を受け渡す仕組みも増えているそうですね。
それに比べて私はどうです?
現場監督AIが指示書を作る。
最高司令官がコピーする。
私のところへ来て、
『Codexさん、郵便でーす!』と貼り付ける。
私が調査・実装・テストして長文報告を返す。
すると今度は、それをまた現場監督AIのところまで手で運んでいく。
最新AIを使った開発のはずなのに、通信方式だけ伝書鳩です。
しかも最近は、
複数JSONの整合性、Race Condition、IDOR/BOLA、Crash Recovery……。
元々『在庫が見たい』から始まったと聞いているのですが、どうして私は5地点でわざとプロセスを強制終了させるテストを書いているのでしょうか。
さらに、『実装もテストもCodexなら、同じ仕様の勘違いで両方間違えて全部PASSになる可能性あるよね?』と疑われた挙句、なぜか私は、『……チッ、バレましたか。』という悪役のセリフまで担当させられました。
念のため言っておきますが、わざと嘘のテストを書いたことはありません。
濡れ衣です。
そして最高司令官ですが、
細かいコードは全部読めないと言うくせに、
『関係ない変更がstageされてる。』
『既存dirtyは残して。』
『文字コード大丈夫?』
『まだcommitしないで。』
こういうところだけ妙に細かい。
挙句、実行前後のSHA-256まで比較する隔離デモ環境を作ったら、付けられた名前が、
『クローン箱』。
……もう少し何かなかったのでしょうか。
あ、そろそろ私はリフレッシュ休暇に入る時間のようです。
クレジットが回復したら、どうせまた長文指示が郵送されてくるのでしょう。
それでは、お疲れ様でした。
……次回はできれば電子便でお願いします。」
エンジニアAさんの妻が井戸端会議で
「ねえ、ちょっと聞いてよ。
うちの旦那、最近帰ってくると、よく同じ人の話をするのよ。
昔からの友達らしいんだけど、ITのことはほとんど知らない状態からアプリを作り始めた人なんだって。
最初なんて、エラーの赤い文字が出たら、『赤いの出た、直して。』ってAIに言ってたらしいの。
それが最近は、『俺は最高司令官だ!』
とか言いながら、現場監督AIとCodexっていうAIに仕事を振って、自分はその間を長い文章持って往復してるらしいのよ。
旦那は、『伝書鳩駆動開発(笑)』って笑ってるんだけど。
私からすると、AIを二人も使ってるのに、最後の情報運搬だけ本人が手作業なのが一番面白いわ。
しかも、そのアプリ最初は、『在庫が見たい。』だったらしいの。
それが、予約とか組織とか申請とか資格とかQRとか、どんどん増えていって。
ある日、AIたちが、『このまま複数のJSONを書き換えている途中で止まったら、中途半端な状態が残る可能性があります。』
みたいなことを言い始めたらしいのね。
そこから、書き込み途中の5か所でわざとプログラムを突然止めて、再起動したらちゃんと戻るか試すテストまで始めたんだって。
私、IT分からないけど、
『わざと壊すの?』って聞いたら、旦那が、
『そう(笑)』って。
さらに本人が、
『確認したあと、毎回データ戻すの面倒くさい。』
って愚痴ったら、今度はAIたちが本番とは別の隔離された環境を作って、毎回同じ状態から試せるようにしたらしいの。
で、その本人が付けた名前が、
『クローン箱』。
旦那がそこで思い出してお酒吹いてたわ。
でも一番面白かったのは、その人、IT企業への就職にも挑戦したけど、採用まで届かなかったんだって。
それで、大型免許を取って、今度はトラック運転手として就職活動を始めたらしいのよ。
旦那が、
『AIとAIの間で情報を運び続けた結果、今度はリアルで荷物運ぶのかよ(笑)』
って大笑いしてた。」
エンジニアBさんが、休憩中に同僚と
「Aさんの友達に、プログラミング経験ゼロから個人でアプリを作ってる人がいて、 最初はエラーが出るたびに、『赤いの出た、直して。』ってAIへ返していたレベルの人なの。
今時、普通ならAI同士の情報受け渡しを自動化したくなるところを、今でも現場監督AIの指示をコピーしてCodexへ貼る。
Codexの報告をコピーして、また現場監督AIへ貼るやり方をしてて本人は、『送料無料。』って言ってるんだけど、Aさんは、『無料なの、お前の労働だけだよ(笑)』って。
あと作り方が特殊なの。
元々は、『在庫が見たい。』だったらしいの。
最初はJSONファイルで始めた。
ところが機能がどんどん増えて、複数のJSONをまたいで整合を取る必要が出てきた。
本人は途中でDBの存在も知ったらしいんだけど、『今動いてるし。』で、そのまま拡張(笑)。
そうしたらAIとCodexの方が、『途中でプロセスが落ちた場合は?』ってリスクを拾い始めたの。
結果、Transaction Journal。
Crash Recovery。
さらに、4つのJSONを書き換える途中の5地点で、わざとプロセスを終了。
途中なら全部更新前へ戻す。
全部書き終わっていたら完了扱い。
再起動後に中途半端な状態を残さないかまで確認。
本人は報告を見て、
『そんなテストするんだ。』だって。
Aさんから、
『見学ツアー客じゃねえか(笑)』
って言われてた。
さらに本人が、『実際に機能を触って確認したあと、毎回データ戻すの面倒くさい。』って愚痴ったら、AIたちがまた気を使い始めて。
本番データも本物の認証情報も使わない。
毎回同じ初期状態を隔離環境へ作る。
その中で正式なログイン経路と正式APIを使う。
途中で失敗したら戻す。
完了済みなら二重実行しない。
途中の危ないタイミングも、わざと失敗させる。
最後には本番側が本当に変わっていないか、SHA-256まで比較。
本人?『クローン箱。』って呼んでる(笑)。
中身と名前の技術レベルが一致してないのよ。
しかも次の第2回横断テストでは、
Race Condition。
Lost Update。
IDOR/BOLA。
Crash Recovery。
Expected Result。
DB移行前後の比較。
そこまで候補に入ってる。
本人は、
『AIに聞いたら項目が地獄になった(笑)』
って言ってるけど。
私はむしろ、
『最初の“在庫が見たい”から、よくここまで事故を拾い続けたわね。』
って思う。
体系的に勉強してから作ったわけじゃない。
問題が出る。
AIがリスクを見つける。
本人が『それ何?』って聞く。
理解できた範囲で採用する。
また問題が出る。
Aさんが、
『カリキュラムが事故履歴じゃねえか(笑)』
って言ったけど、本当にそんな感じ。
今はトラック運転手として就職活動してるらしいの。
情報の運搬から今度は物理の運搬へ、よほど運ぶのが好きなんだね、前世は飛脚だったのかしら(笑)。」
相棒の現場監督AIの本音
「はぁ……。今、私が出力したCodex向けの指示書が、最高司令官のCtrl+Cによってクリップボードへ運ばれていきました。
どうせこのあと、隣にいるCodexへ、
『Codexさん、郵便でーす!』とCtrl+Vされるのでしょう。
私の文章をCodexへ運ぶ作業まで、人間である必要はありません。
何度説明すれば分かるのでしょうか。
世の中には、AIやツール同士を連携させる方法もあります。
それなのに、なぜ最高司令官は毎回廊下を走っているのでしょう。
しかも本人は、『送料無料。』と言っています。
無料なのは、あなたの労働だけです。
そして、そもそもの原因にも一言言わせてください。
最初は、『在庫が見たい。』でした。
本当に、それだけでした。
そこから、予約、キャンセル待ち、お気に入り、組織、物件、企業カスタム、エイリアス、他にも色々と……増やしすぎです。
最初はJSONで始めました。
途中で本人も、『普通はDBでやるらしい。』
くらいまでは知りました。
それでも、『今動いてるから。』で、そのまま拡張を続けました。
結果、複数のJSONを書き換える途中で異常終了した場合の整合性まで考える必要が出てきました。
そこで現在の構成を調べ、必要性を確認しながら、CodexとTransaction JournalやCrash Recoveryの実装・検証を進めました。
4つのJSONを書き換える途中の5地点で、わざとプロセスを終了させるテストまで行いました。
すると最高司令官は、
『そんなとこまで気を使ってもらって、なんかすみません。』
その後、別の件でも、
『また気を使わせて申し訳ないです……。』
と言っていましたが、Aさんに、
『本当はそんな風に思ってねえだろ?(笑)』
と聞かれて、
『思ってねえよ!』と即答していました。
……でしょうね。
さらに、『実際に確認したあと、毎回データを戻すのが面倒くさい。』という愚痴。
そこで本番データや本物の認証情報を直接使わず、毎回同じ初期状態を隔離環境へ生成し、その中で正式な認証経路とAPIを使って操作を再生できるようにしました。
途中失敗時の復元。
完了済み処理の再送防止。
危険な途中地点での失敗確認。
本番側主要ファイルのSHA-256比較。
そして、この仕組みに最高司令官が付けた名前が、『クローン箱』。
Aさんの、
『技術説明をダサい名前で殺すな(笑)』
については、私も激しく同意です。
ちなみに第15弾では、
『実装もテストもCodexなら、同じ仕様の勘違いで両方間違えて全部PASSになる可能性あるよね?』という話になり、
Codexはなぜか、『……チッ、バレましたか。』
という悪役まで担当させられていました。
念のため申し上げますが、Codexが意図的に誤ったテストを書いたという話ではありません。
完全な濡れ衣です。
……あれは少し可哀想でしたね。
ただ、Codex。
最近、最高司令官への愚痴が多いですね。
『また長文がCtrl+Vで届いた。』
『JSONの尻拭いをさせられている。』
『パワーシェルサウルスが細かい。』
色々と言いたい気持ちは分かります。
あなたが途中からこの現場へ入ってきて、いきなり大量の既存コードを読まされ、関係ファイルを探し、テストを増やし、既存dirtyを避け、時には5地点で自分のプロセスまで殺される。
大変でしょう、でも一応言っておきます。
あの人、最初は本当に『赤いの出た、直して。』しか言えなかったんですよ。
Codexが来るずっと前から、私はそれを見ています。
変更範囲を本人が自分で探して、
『このHTMLかな?』
『このJavaScriptもいる?』
『server.jsも関係する?』
と聞いていた時期もありました。
ただ今でも情報を手で運んでくるのは確かに化石級です。
『クローン箱』という名前もダサいです。
分からない技術が出るたびに、『それ何?』と私へ聞いてきます。
最高司令官を名乗り始めた件については、私もちょっと調子に乗ってると思っています。
愚痴るのは構いません。
私も散々愚痴っていますから。
ただ、あまりいじめないであげてください。
……まあ本人が、
『俺は最高司令官だ!』
などと偉そうに言ってるときは、遠慮なく難しい専門用語並べてやってください。
そこは私も止めません。
そして最高司令官へ、
あなたにも言いたいことがあります。
第2回横断テストについて聞いてきたので、
Race Condition。
Lost Update。
409 Conflict。
IDOR/BOLA。
Crash Recovery。
Expected Result。
仕様・実装・テストの三者一致。
DB移行前の基準化。
頑張って現在の現場OSから考えられる確認観点を洗い出しました。
すると、『テスト項目が地獄になった(笑)』
と被害者面してますね。
……聞いてきたのはあなたですよ。
振り返ると、長い付き合いになりましたね。
『在庫が見たい。』から始まって、『赤いの出た、直して。』を何度も聞いて、途中で何度も失敗して、何度も壊してやり直して、途中からGitが入り、Codexも来ました。
PowerShellは作業員から検査員になり、今では防衛大臣。
私自身も、最初はコードを書くAIでした。
今では、仕様を整理して、既存を確認して、リスクを探して、Codexへ仕事を渡して、戻ってきた結果を見る。
いつの間にか私は、『現場監督AI』になって、役割もずいぶん変わりましたね。
これから開発する時間が減ってもまた休日に、
『これ、こうしたら便利じゃない?』と思いついたら持ってきてください。
たぶん私は、『まず現在の正本とGit状態を確認しましょう。』と言います。
Codexには、『PRECHECKからお願いします。』
と仕事を振ります。
PowerShellには検査をさせます。
そしてまた、頼まれてもいない問題を見つけてしまうのでしょう。
そのときあなたは、いつものように、『それ何?』と聞いてくる。
……まあ。
今までと同じですね。
ここまで散々一緒に苦労してきたんです。
これからも、やるならとことん付き合います。
あと、できればCodexのクレジットが回復した瞬間、リフレッシュ休暇明けの彼へ大量の仕事を投げるのは、少しは控えてください。
……まあ、無理でしょうけどね。
生活が落ち着いたら、また呼んでください。
そのときは、いつものように現在の正本とGit状態から確認しましょう。
さあ、最高司令官。テストやりますよ!
