正規表現ひとつで、台帳と実装がズレていた話
「使ってよいクラス名」を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つになるように見えます。実際には、散文の書き間違いまで機械の判断へ入ってしまうことがある。正本を無理に共有するより、役割を分けて双方向の同期をテストするほうが、結果としてずれにくい構成でした。
今回は以上です。