個人開発の検証で「成功」表示を信じて確認できていなかった7件。最後はその分析自体が同じ穴でした

転職用のポートフォリオとして、個人でWebアプリ「Hubpin」を作っています。
制作の経緯は「Hubpin 開発記録」に時系列でまとめています。
設計と判断は自分で行い、実装も原則自分で書いています。AIには手順書・レビュー・調査を任せています。

このアプリは、いくつかのサービスから記事の一覧を取ってきて、1つのページにまとめて出すものです。

目次

1日に7回、同じ形で間違えた

この日の検証は、完了条件を書くのも、測るのも、AIに任せていました。
AIは2つ使い分けています(記録係と実装係)。
自分は結果を読んで、直すかどうかを決める側です。
その日に7件、同じ形の間違いが出ました。
どれも画面には「成功」が出ていて、どれも実際には確認できていなかった。

5件目は返ってきた数字が正解で、6・7件目は間違いを直している最中に出ました。
だから、後ろほど見つけにくかったです。

1件目: 完了条件が「0個でも満たせる」書き方だった

工程の完了条件を、記録係のAIがこう書いていました。

3つのカードがすべてリンクとして機能する

3つのカードは、それぞれ別のサービスから記事を取ってきて並べる場所です。
取ってくる処理を「アダプター」と呼んでいて、この工程はそれを3つ作る工程でした。

この条件は、アダプターが1つも動かなくても満たせます。
カードそのものがリンクになっていればいいので、中身が空でも真になる。

実際、表示の枠を作った時点で、3カードとも条件を満たしていました。
作る前から合格していた、ということです。

直した形はこうです。

3カードともエントリーが1件以上並び、各エントリーが個別ページへ飛ぶ

2件目: 4本つないだコマンドの、後ろ2本が8日間走っていなかった

作業の区切りで、毎回4本のチェックを走らせていました。

npx tsc --noEmit && npm run lint && npm run format:check && npm run test:run

&& でつないでいるので、どれかが落ちた時点でそこから後ろは走りません。
3本目の整形チェックが落ちていたので、4本目のテストは一度も実行されていなかった。

それに気づかなかった理由は、直後に別のコマンドを打っていたからです。

npm run build   ← 別に打ったので「成功」して見えた

ビルドが通って、画面には緑の出力が残る。
テストが走っていないことは、画面のどこにも出ません。

CI 側も同じでした。
テストとビルドは 0秒でスキップされていて、通っていたのは lint だけ。
整形漏れは 8日ぶん、4つのコミットにまたがって溜まっていました。

3件目: 自分で付けたヘッダで、自分のツールが空ページを保存していた

記録用にスクリーンショットを撮るスクリプトを使っています。
幅が 500px 未満のときは、ページを iframe に入れて撮る方式に切り替わります。

自分のサイトには X-Frame-Options: DENY を付けていました。
他のサイトの iframe に埋め込まれないようにする設定です。

つまり、自分のスクリプトが、自分のサイトを iframe に入れられない。

エラーは返りません。
「390×1257 で保存した」と成功で終わって、中身は約4KBの灰色の空ページでした。
画像を開くまで気づけない。

ヘッダは正しく効いた結果なので、設定を疑う理由がどこにもなかったです。

4件目: 0 は返ってきたが、ログイン画面を数えていた

アクセス解析のタグを本番だけに入れました。
なので「プレビュー環境には入っていない」ことを、AIが完了条件に書きました。

curl -s https://<preview>.vercel.app/ | grep -o "G-XXXX" | wc -l
→ 0

AIが測って 0 が返り、自分はそれを見て「入っていない」と判断した。

実際に返っていたのは 302 リダイレクトで、中身は 15バイト。
飛び先は SSO のログイン画面でした。
プレビュー環境には保護がかかっていて、ログインしないと本物のページが見えない。

ログイン画面に測定タグが無いのは当たり前です。
この 0 は何も証明していなかった。

しかも保護が有効なかぎり、この完了条件は永久に通ります。
タグが入っていても入っていなくても 0 が返る。

直した形は、本物のページに到達したことを先に確かめてから 0 を数える、です。
ログイン済みのブラウザで、そのページにしか無い文言が本文に入っているかを見る。
証人を立ててから数える。

5件目: grep -c は「行数」だった。しかも正解を返していた

本番の確認には、AIがこう書いていました。

curl -s https://<本番>/ | grep -c "G-XXXX"
→ 1

-c は「マッチした行数」で、出現回数ではありません。
返ってくる HTML はほぼ改行の無い1行なので、何箇所あっても 1 が返る。

実際は2箇所ありました。
<link rel="preload"> の href と、ページのデータの中の "gaId" です。

ただ、「1以上なら OK」という判定としては正しく動いてしまう。
だから実害が出ない。

完了条件には「curl で数えた」と書いてありました。
数えていなかったです。

6件目: 5件目を指摘したメッセージの中で、記録係のAIが5件目を踏んでいた

grep -c の件を、記録係のAIが実装係のAIへ報告しました。
そのメッセージに、こう書いてありました。

実測で確かめた3点:
<script> は 0件 / <link rel="preload"> は 1件 /
本物のページの証人「Hub a nice trip」は 1件

最後の1件が間違っていました。実際は2件。
そこだけ grep -c で数えていた。

grep -c  "Hub a nice trip"           → 1   ← 報告した数字
grep -o  "Hub a nice trip" | wc -l   → 2   ← 実際

grep -c は行数だから使うな」と書いている、その同じメッセージの中で使っていた。

そこで記録係のAIは、なぜ1箇所だけ落ちたのかを整理しました。
3つのうち2つは無事だったからです。

分かれ目は、実物を出させたかどうかに見えました。
こう整理しました。

何を見たか 結果
link —— grep -o でタグの実物を出力し、1行だったので1件 正しい
script —— grep -o の出力が空だったので0件 正しい
証人 —— 件数だけ取った 間違い

実物を見た2箇所は無事で、件数だけ取った1箇所が落ちた。
筋が通って見えたので、自分もこの整理で納得しかけました。

7件目: その対照表が、対照になっていなかった

表が実装係のAIに送られ、再実測が返ってきました。

パターン 真の件数 grep -c `grep -o \ wc -l`
<link ... googletagmanager ...> 1 1 1
<script ... googletagmanager ...> 0 0 0
Hub a nice trip 2 1 2
G-XXXX 2 1 2

HTML が1行なので、真の件数が 0 か 1 のときは -c-o が必ず一致します。

つまり linkscript は、grep -c で数えていても正解していた。
「実物を見たから無事だった」の対照群は、そもそも失敗できなかった。

実物を出したことが効いたのか、試されなかっただけなのか。
この3件では区別がつきません。

前半3件とまったく同じ形が、その分析の中に出ていました。
偽になる条件を持っていない。
教訓を書くために作った表が、教訓の対象でした。

7件は3種類に分かれた

前半3件は、偽になる条件を持っていません。

  • 完了条件は、0個でも真になるので満たせないことがない
  • && は、落ちた時点で後ろが走らないので後ろが赤くなりようがない
  • 撮影は、空でも保存が成功するので失敗として現れない

中盤2件は、数字が返ってきています。ただし数えた対象が違う。

  • プレビューの 0 は、ログイン画面を数えた 0 だった
  • grep -c の 1 は、行数を数えた 1 だった

最後の2件は、それを直そうとした側で起きました。

  • 6件目は、指摘しているメッセージの中で同じ手を使った
  • 7件目は、その原因を分析した表が前半3件の形をしていた

後ろへ行くほど見つけにくくなります。
前半は「確認していない」と気づく余地がある。
中盤は、数字という証拠が手元にある。
しかも5件目は、その数字が偶然正解と一致していた。
最後の2件に至っては、間違いを直している最中の手つきそのものでした。

注意では防げなかった。効いたのは出力の形

記録係のAIは、知った直後に、しかもそれを説明している最中に、同じ手で踏んだ。
その原因を分析した表が、また同じ形だった。
「今日から気をつける」で防げるものではなかったです。

対策として残したのは「-c を使わない」ではなく、数字ではなく実物を出させる

ただし、その根拠は7件目で崩れた対照表ではありません。
仕組みのほうにあります。

  • -c の出力は数字だけ。何を数えたかが入っていない
  • -o の出力には、数えた実物が入っている

実際、今回の突き合わせが成立したのは、-o の出力にタグの実物が残っていたからでした。
これは対照実験ではなく仕組みの説明なので、反例が出ても崩れません。

数字は、何を数えたかを持っていない。
だから数字だけを見て、手続きは復元できない。

7件目は、まさに「数字が同じだから数え方も同じだろう」という推測から始まっていました。

目次