正規表現のバグの大半は構文エラーではありません。パターンが完全に有効でも間違っていることがあるのです。多すぎる、少なすぎる、あるいは最悪、敵対的な文字列に出会ったときにページが固まります。頭の中の例で動くパターンと、実データで動くパターンの間にあるのがテストです。
この記事では、ブラウザで正規表現を書いてテストしデバッグする実践的なワークフローと、最もよく噛みついてくる失敗パターンを扱います。
本物のデータでテストする
おもちゃの文字列はパターンを甘やかします。実際の入力には、忘れられた改行、末尾空白、絵文字、空の値、そしてコピーしてきたマークアップが40キロバイトも含まれています。
- 手持ちで最悪の実サンプルを貼る — 理想は既に何かを壊したあのサンプルです。
- 空文字列・1文字だけの文字列・非常に長い1行を含める。
- 入力が含みうるなら非ASCIIテキストを含める:
Ü、日本語、🙂。 - 最初のマッチだけでなく件数を見る。 オフバイワンの量子化子や貪欲な重なりはここでしか現れません。
テスト時にライブハイライトを伴えば、この手間は安上がりです。パターンを編集するたびにマッチ位置が更新され、3日後にログファイルで顔を出しません。
フラグはパターンの意味を変える
同じ式でも、付けるフラグによって挙動は変わります。
| フラグ | 効果 | 典型的な意外な点 |
|---|---|---|
i |
大文字小文字を区別しない | 区別したかった識別子にもマッチする |
g |
グローバル — すべてのマッチを検出 | JavaScriptではtest()とexec()が呼び出し間で状態を保持する |
m |
複数行 — ^と$が行ごとに働く |
^はもう「文字列の先頭」の意味ではない |
s |
ドットが改行にもマッチ | .*が行の境界を越え始める |
u |
Unicodeモード | コードポイント単位のエスケープに必須 |
JavaScriptのgフラグには特に注意が必要です。付けるとregex.test()はlastIndexを進めてしまい、同じオブジェクトへの繰り返し呼び出しが別の結果を返します。「一度は動くが再試行で失敗する」バグの古典的な原因です。
キャプチャグループと名前付きグループ
グループはデータを抽出しますが、メモリも消費し、パターンの可読性も下げます。
// 名前付きグループは並べ替えに耐え、番号付きは耐えない
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const m = "2026-09-22".match(re);
m.groups.year; // "2026"
m.groups.month; // "09"
番号付きより名前付きグループを選び、パターンの改修で参照が黙ってずれないようにしてください。グループ化だけで取得が不要な場合は非取得グループを使います: (?:...)。
カタストロフィックバックトラック
(a+)+$や(\s*)*のような入れ子の量子化子は、ほぼ一致する文字列に対してエンジンを指数時間に追い込みます。良い入力では瞬時に終わり、敵対的な入力で固まる — そして攻撃者が送ってくるのはまさに後者です。
- すでに量子化子を含むグループに量子化子を重ねない。
- 可能ならアンカーする。
^と$でエンジンを早期に棄却させます。 - ほぼ一致しない長い文字列で負荷をかける。 文字を足すたびにマッチ時間が急増するなら、そのパターンは安全ではありません。
- サーバー側の信頼できない入力には線形時間エンジン(RE2系)を検討します。
パターンを読みやすく保つ
- コード内でパターンに名前を付ける —
const ISO_DATE = /.../が、6ファイルに繰り返す匿名リテラルより優秀です。 - エンジンが対応するならフリースペースモード(
x)を使って、パターン内にコメントと空白を置きます。 - 桁には明示的である。
[0-9]はASCII数字;Unicodeモードの\dは他の文字種にもマッチします。 - パターンのそばに例を2つ残す。 マッチすべきものと、マッチしてはいけないものを。
実践的なワークフロー
| 手順 | 確認すること |
|---|---|
| 1. 実サンプルにマッチする最小のパターンを書く | ハイライトが期待どおりの位置に落ちる |
| 2. マッチしてはいけないサンプルを追加する | 失敗すべき入力でマッチしない |
| 3. フラグを意図的に付ける | すべてのフラグに理由がある |
| 4. 長く敵対的な入力で負荷をかける | マッチが瞬時に終わる |
| 5. 名前付きグループを使ってコード化する | 位置による参照が残っていない |
試してみる
正規表現テスターを開き、パターンと実サンプルを貼り付けて、入力のたびにすべてのマッチがハイライトされるのを見てください。テストはブラウザ内で動くため、検証中のテキストが端末から出ることはありません。サンプルにトークンや鍵、顧客データが含まれていれば、それがどれほど重要なのかがわかります。