転職用のポートフォリオとして、個人で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 が必ず一致します。
つまり link と script は、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件目は、まさに「数字が同じだから数え方も同じだろう」という推測から始まっていました。
