正規表現ひとつで、台帳と実装がズレていた話

2026.08.21 09:00
2026.08.30 00:36
正規表現ひとつで、台帳と実装がズレていた話

「使ってよいクラス名」をMarkdownの台帳へまとめ、その同じファイルをチェックスクリプトにも読ませていました。人間向けの説明と機械の検証を1か所で管理できるので、扱いやすい設計だと思っていました。

ところが、たった1つの正規表現がcamelCaseのクラスを黙って無視し、台帳と実装が両方向にずれ始めました。エラーは出ないのに、存在しないクラスが通り、正しいクラスが弾かれる。その原因と、最終的に正本をどこへ置いたかを整理します。

小文字限定の正規表現が原因

/\bc-[a-z0-9_-]+\b/    // c-cardListにはマッチしない
/\bc-[A-Za-z0-9_-]+\b/ // マッチする

実装にcamelCaseが混じっていたため、小文字限定の抽出処理が黙って無視していました。存在しないクラスが通り、正しいクラスが弾かれても例外は出ません。

同じ意図の正規表現が複数箇所にコピーされている場合、すべてを機械的に直すのも危険です。別経路で処理され、実害のない箇所もあるため、実際の入力で挙動を確認します。

正本をコードへ置く

人間向けの台帳を機械検証の正本にせず、コード上の定義を正本にします。台帳はテストで同期が保証されたビューとして扱います。

同期テストは双方向に必要です。

台帳にあって実体にない → 幽霊エントリ
実体にあって台帳にない → 登録漏れ

別名は例外処理にせず、rootを持つ別名、総称、未実装といった構造で定義します。「宣言に書けるID」と「生成物に書ける実クラス」も別の集合で検証します。

実装あり判定にも注意する

複合セレクタ内の死んだクラスを拾うと、実装済みと誤判定します。一方、rootを持たずBEM要素だけを定義するCSSもあります。行頭の空白を許容しつつ、実際のセレクタ構造を確認します。

/^\s*\.([a-zA-Z0-9_-]+)/m

最終的には、機械が判断する正本をコードへ戻し、Markdownの台帳は同期をテストされた人間向けのビューとして扱うようにしました。別名や未実装も例外的な文字列ではなく、意味を持った構造として定義しています。

人と機械で同じファイルを使えば、管理が1つになるように見えます。実際には、散文の書き間違いまで機械の判断へ入ってしまうことがある。正本を無理に共有するより、役割を分けて双方向の同期をテストするほうが、結果としてずれにくい構成でした。

今回は以上です。