個人開発で Web サイトを運用していると、同じ種類の不具合を数か月おきに踏み直します。そのたびに書き足してきたメモが、気づけば1,549行になっていました。
Cowork に「この症状は前にも踏んだはずです」と頼むたびに、私はそのファイルを丸ごと読ませていました。症状は一つなのに、渡しているのは105KB近い全文です。読み込みを待つ時間の長さに、ある日さすがに手が止まりました。
そこで、番号を言えば該当の一節だけが返ってくる形に変えました。ところが最初のスクリプトは、番号の大半を引けなかったのです。その失敗も含めて、数字で残しておきます。
先に結論 — 節は小さく、問題は「引けない番号」でした
実測した結果を先に置きます。対象は、Web サイト運用で踏んだ不具合を書き溜めた Markdown 1 本です。
| 項目 | 実測値 |
|---|---|
| ファイル全体 | 104,822 バイト(1,549 行) |
| 見出しの数(## と ###) | 217 |
| 節の大きさ(中央値) | 356 バイト |
| 節の大きさ(上位10%の境目) | 910 バイト |
| いちばん大きい節 | 4,008 バイト |
| 索引(章と番号つき見出しだけ) | 144 項目・11,740 バイト |
| 番号で一節だけ引いたとき | 401〜1,567 バイト |
全文を渡すのと、番号で一節を引くのとでは、渡すバイト数が60〜260分の1ほど違います。節の中央値が356バイトという小ささは、測るまで想像していませんでした。
メモは「読ませるもの」ではなく「引くもの」にします。 この線引きは、数字を見てから決まりました。
最初の抽出スクリプトは、番号の4分の1しか引けませんでした
最初に書いたのは、見出しに #119 のような番号が付いた節だけを拾う awk でした。
# 最初の版: "### #119 ..." の形だけを拾う
awk -v n="$2" '
/^#{2,3} / { on = ($0 ~ ("^#{2,3} #" n "([^0-9]|$)")) }
on { print }
' "$1"#119 は引けました。1,567 バイトが返ってきて、ここまでは順調でした。
つまずいたのは、普段よく参照する #70 を引いた瞬間です。結果は 0 バイトでした。エラーは出ません。ただ何も返ってこないだけです。
見出しを数え直して、理由が分かりました。
### #119 ...のように#付きで番号を持つ見出し: 異なる番号で 30 個## 70. ...のように#なしの章番号を持つ見出し: 残りの大半
メモは何か月もかけて育ってきたため、後半に足した節だけが #番号 方式で、前半は章番号方式だったのです。最初のスクリプトが引けたのは 30 個の番号で、実際に引ける番号は 120 個(1〜134 のうち)ありました。4分の1です。
しかも「0 バイトで何も出ない」ことは、Cowork 側から見ると「該当なし」と区別がつきません。メモに書いてあるのに「前例なし」と判断されかねない——この点がいちばん怖いと感じました。
直した版と、引いたときの実測
章番号の節(## 70.)は次の ## までを子の ### ごと返し、#番号 の節はその節だけを返すようにしました。
#!/bin/bash
# kb.sh FILE NUMBER
# "## N." は次の "## " まで(子の ### を含む)、"### #N" はその節だけを出す
awk -v n="$2" '
/^## / { on = ($0 ~ ("^## " n "\\.")); top = on; if (on) print; next }
/^### / { if ($0 ~ ("^### #" n "([^0-9]|$)")) { on = 1; top = 0; print; next }
else if (!top) { on = 0 } }
on { print }
' "$1"直したあとの実測です。
| 引いた番号 | 返ったバイト数 | 中身 |
|---|---|---|
| #70 | 401 | スケジュールタスクの手順書が更新と同期されない問題 |
| #19 | 1,531 | 表示速度の改善(子の節 3 本を含む) |
| #119 | 1,567 | hreflang の x-default が二重に宣言される問題 |
| #125 | 1,016 | ルートの設定を子ページが継承してしまう問題 |
| #999(存在しない番号) | 0 | 該当なし |
10 回続けて走らせても 0.057 秒でした。速度は問題になりません。
最後の行が大事です。存在しない番号が 0 バイトなのは正しい挙動ですが、実在する番号が 0 バイトなのは壊れている状態です。この二つを見分けるために、次の節の確認を足しました。
「0 バイト」を黙らせない — 引く前に索引と突き合わせます
実在の番号なのに引けない事態を防ぐため、索引を先に作っておき、番号が索引にあるのに抽出が 0 バイトなら止めるようにしました。
#!/bin/bash
# kb-check.sh FILE — 索引にある全番号を引いて、0 バイトのものを数える
F="$1"
nums=$( { grep -oE '^## [0-9]+\.' "$F" | grep -oE '[0-9]+'
grep -oE '^### #[0-9]+' "$F" | grep -oE '[0-9]+'; } | sort -nu )
bad=0
for n in $nums; do
size=$(./kb.sh "$F" "$n" | wc -c)
[ "$size" -eq 0 ] && { echo "0 bytes: #$n"; bad=$((bad+1)); }
done
echo "checked=$(echo "$nums" | wc -w) zero=$bad"メモに節を足したあとに、これを一度回します。手元では checked=120 zero=0 と出て、0.8 秒で終わりました。zero=0 ならそのまま使えますし、0 以外なら見出しの書き方が新しい型になっているというサインです。
行番号つきの索引にしなかったのも、一つの線引きです。行番号は節を足すたびにずれます。見出しの文言と番号で引いておけば、メモが育ってもスクリプトは壊れません。
Cowork へ渡す指示の書き換え
仕組みができたので、Cowork への指示も書き換えました。
変更前は、次のように書いていました。
つまづきの記録は
STUMBLING_POINTS.mdにあります。関係しそうな箇所を探してください。
変更後は、次のとおりです。
つまづきの記録は
STUMBLING_POINTS.mdにあります。全文は読まず、まずkb.shで番号を指定して一節だけ引いてください。番号が分からないときは、索引(見出しの一覧)を先に見て、該当しそうな番号を選んでください。引いて 0 バイトだった番号があれば、黙って進まずに私へ知らせてください。
最後の一文は、さきほどの失敗から足したものです。「何も出ない」を黙って受け流させない——それだけで、取りこぼしに気づける場所が一つ増えます。
まとめて、次にやること
数字で言えば、約105KBを毎回渡していたものが、約0.4〜1.6KBの一節で済むようになりました。ただ、数字よりも残ったのは、引く仕組みは、引けなかったときの挙動から先に決めるという感覚でした。
まずお手元の運用メモで、見出しの番号の付け方が 1 種類に揃っているかを数えてみていただければと思います。私は揃っていませんでした。揃っていないと分かったところから、この仕組みは役に立ち始めたのだと思います。