検査の型
AIに書き換えさせた結果を検査するコードの書き方|数えるだけの検査は通ってしまう
100ファイルの書き換えは失敗しません。失敗するのは、その100ファイルが意図どおりかを人間が判断できなくなるところです。検査コードを書くわけですが、その検査コード自体がよく間違っています。実際に出した間違いを並べます。
このサイトはAnthropicの公式サイトではありません。Claude Codeの機能・料金・提供範囲は変わる可能性があるため、重要な判断の前には公式のドキュメントと自分のアカウント設定を確認してください。
絶対数で見ると偽陽性が出る
HTMLを書き換えたあと、構造が壊れていないかを見るために開始タグと終了タグの数を数えました。
const open = (html.match(/<div\b/g) || []).length;
const close = (html.match(/<\/div>/g) || []).length;
if (open !== close) console.log('壊れている: ' + url);
これを流すと大量に「壊れている」が出ました。ところが調べると、変更前から数が合っていませんでした。閉じ忘れのまま何年も動いているページがあったわけです。ブラウザは補正して表示するので、誰も困っていませんでした。
つまりこの検査は、自分の変更とは無関係な既存の状態を拾っていました。見るべきなのは絶対数ではなく変更によって差が変わったかどうかです。
const diff = h => (h.match(/<div\b/g) || []).length - (h.match(/<\/div>/g) || []).length;
if (diff(before) !== diff(after)) console.log('開閉差が変わった: ' + url);
こう書き直すと、既存の不均衡は無視され、自分が壊した場合だけが出ます。検査の対象は「正しい状態」ではなく「自分の変更」です。
探す範囲が狭いと「無い」が出る
全ページのh1を点検したとき、「h1が空のページが多数ある」という結果が出ました。報告する直前に確認したら、間違っていました。
原因は、<main> の中だけを対象に探していたことです。そのサイトのh1は <main> の外側にありました。範囲を全体に広げて測り直すと、1,018ページでh1の欠落はゼロでした。
ここから決めたのは、「0件」や「全滅」という結果が出たら、まず測り方を疑うということです。実運用しているサイトで全ページに同じ欠陥がある、というのは起こりにくい話です。極端な数字は、たいてい検査側の問題です。
否定形の判定は取得失敗も通す
あるドメインがサーバーに登録済みかどうかを、初期画面の文言で判定しました。
const ok = !/webserver is functioning normally/.test(res.body);
応答が取れずbodyが空文字のときも、この式は true になります。結果、未登録のドメインが「登録済み」と表示されました。
否定形で書いた判定は、対象が存在しないときにも通ります。書くなら取得に成功したことを先に確かめてから中身を見る順です。
if (res.status !== 200 || res.body.length === 0) return '取得できなかった';
return /webserver is functioning normally/.test(res.body) ? '未登録' : '登録済み';
「取得できなかった」を、合格でも不合格でもない第三の値として返すのが大事です。二値に押し込むとどちらかに誤って混ざります。
書き換えが副作用を作っていないか
リンクの張り替えをしたときに、ページが自分自身へのリンクを持つ状態を作ってしまいました。統合先のURLへ一括で付け替えたので、統合先のページ自身にも自分宛てのリンクが残ったわけです。
検出は簡単で、書き換え後に「リンク先が自分のパスと同じもの」を数えます。消すときに引っかかったのは次の2点でした。
- アンカーだけ消すと
<li></li>が残る。空の項目が並びます。<li>ごと消す処理を先に当てて、それから素のアンカーを消しました hrefが最初の属性とは限らない。<a class="..." href="...">の形があるので、属性の順番を決め打ちした正規表現だと取りこぼします
一括処理では、「意図した変更が入ったか」と同じくらい「意図しない変更が入っていないか」を測る必要があります。前者だけ見ていると、副作用に気づけません。
標本検査で「ok」は証明にならない
56ドメインに同じ設定を入れたあと、数件だけ確認して「問題なし」と判断したことがあります。あとで全数を測ったら、いくつか漏れていました。
数件で確認したくなるのは、全数だと時間がかかるからです。ただ、一括処理の失敗はたいてい特定の条件を持つファイルだけで起きます。ファイルの構造が違う、既に別の設定が入っている、といった理由です。無作為に数件選ぶと、その条件を持つファイルに当たらない確率のほうが高いわけです。
全数が現実的でないほど多い場合は、失敗しそうな条件を先に挙げて、その条件に当てはまるものを全部見るようにしています。「一番古いファイル」「一番大きいファイル」「別の設定が既に入っているファイル」といった具合です。
反映が揺れる環境では複数回見る
サーバーにキャッシュが挟まっていると、設定を変えた直後は301と200が行ったり来たりします。1回だけ見て「301になっていない、失敗だ」と判断すると、実際には成功しているのに戻す、ということが起きます。
対処として、確認は間隔をあけて複数回、期待する結果が出たらそこで打ち切る、という形にしました。逆に、期待する結果が最後まで出なかったときだけ復旧処理に進みます。
let r = null;
for (let i = 0; i < 4; i++) {
await sleep(1500);
r = await check(host);
if (r.ok) break;
}
if (!r.ok) await rollback(host); // 4回見て駄目なら戻す
「複数回見る」と「復旧する」をセットにしておくと、揺れで誤判定しても被害が出ません。
まとめ
| やりがちな検査 | 何が起きるか | 直し方 |
|---|---|---|
| タグの絶対数を数える | 既存の不均衡を拾って偽陽性 | 変更前後の差分を比べる |
| 特定の要素の中だけ探す | 外にあるものを「無い」と判定 | 0件が出たら範囲を疑う |
| 否定形で判定する | 取得失敗も合格になる | 取得成功を先に確認し、三値で返す |
| 意図した変更だけ数える | 副作用に気づけない | 意図しない変更も数える |
| 数件だけ見る | 条件付きの失敗を取りこぼす | 全数、または失敗条件を列挙して全部見る |
| 1回だけ見る | キャッシュの揺れで誤判定 | 複数回見て、駄目なら復旧 |
全部に共通しているのは、検査が「通った」ことと、作業が「正しかった」ことは別だということです。検査が何を見ていないかを言えるようにしておくと、報告の精度が上がります。
よくある質問
テストを書くのと何が違いますか?
考え方は同じです。違うのは対象が自分で書いたコードではなく、既に何年も動いている他人の生成物だという点です。あるべき状態が分からないので、「正しさ」ではなく「変更による差」を見ることになります。
検査コードもAIに書かせていいですか?
書かせています。ただし検査コードこそ、何を見ていて何を見ていないかを人間が理解しておく必要があります。ここに挙げた間違いは全部、コードとしては正しく動いていました。
全数検査は時間がかかりませんか?
かかります。ただし1,000ページ程度なら数分です。作業本体より検査に時間がかかることは普通にあり、それでいいと思っています。