# 私が実際に使用する20の正規表現パターン(他の200を大量削除した後)
💡 重要なポイント
- 正規表現マキシマリストからミニマリストへの私の旅
- 正規表現がほぼ私たちのAPIをダウンさせたとき
- 実際に重要なことを分解する
- 大量削除を生き残った20のパターン
私は一度、メールバリデーションのために847文字の正規表現を書きました。それは私の人生の3時間を奪うもので、入れ子の先読み、文字クラスの例外、そして目がしみるほどのバックスラッシュが含まれていました。私はそれをとても誇りに思っていました。「これで全てのエッジケースを処理できる」と自慢のメッセージと共に、私たちのチームのSlackに投稿しました。
その後、誰かがRFC 5322をリンクしてくれました。
幸運にも知らない方のために、RFC 5322は公式なメールアドレスの仕様です。技術的に合法なすべてのメールアドレスを検証する実際の完全な正規表現パターンは6,000文字以上です。それには、かっこ内のコメント、エスケープされた文字を含んだ文字列、四角いブラケット内のドメインリテラルなどが含まれています。技術的には、`"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com`は、仕様に従った有効なメールアドレスです。
私は自分の847文字のパターンを見つめました。そしてRFCを見て、再び自分のパターンへ。そうして、典型的な開発者がすることである、`/.+@.+\..+/`に置き換えて、生活を続けることにしました。なぜなら—誰も実際にこれらのエッジケースを使用しないからです。もしそうなら、壊れることを受け入れるべきです。
それは5年前のことです。それ以来、私は数百の正規表現パターンを書いてきました。上級開発者を泣かせる正規表現のデバッグを行い、本番環境のスローダウンを引き起こしていたパターンを最適化しました。そしてそのすべてを通じて、私は重要なことを学びました:ほとんどの正規表現パターンは、あなたが決して必要としないゴミです。
正規表現マキシマリストからミニマリストへの私の旅
私はかつて、一部の人々が切手を集めるように正規表現パターンを集めていました。私はあらゆる想像可能なパターンを含む巨大な `regex-library.js` ファイルを持っていました。ゾーンIDを持つIPv6アドレス。ルーンアルゴリズムによるクレジットカード番号の検証。あらゆる珍しいプロトコルを処理するURL。1930年代のエリア番号検証付きの社会保障番号。
そのファイルは3200行でした。私は自分が価値のあるものを作っていると確信していました—あらゆるプロジェクトで時間を節約する包括的なライブラリです。私は例とパフォーマンスベンチマークを含む文書の作成を始めました。
それから、私は仕事を変えました。
新しい会社では、私は愛する正規表現ライブラリをコードベースにインポートしようとしました。上級アーキテクトは、コードレビュー中にそれを一目見て、シンプルな質問をしました。「この中で、実際に過去6ヶ月間で使用したものはどれですか?」
私はハイライターを使ってファイルを見て回りました。200以上のパターンの中で、私はおそらく15しか使っていませんでした。他は「念のため」のパターン—問題を探している解決策。私は知的に興味深いから書いたパターンではありましたが、実際の問題を解決するためではありませんでした。
そこから、私は大規模な正規表現の一掃を始めました。私はすべてのパターンを見て回り、「本番環境で必要だったことはありますか?『いつか必要かもしれない』ということではなく、実際に必要だったことはありますか?」と自問しました。答えが「いいえ」なら、それを削除しました。容赦なし。『でも、何があった場合』の例外もありませんでした。
ファイルは3200行から400行になり、次に200行、そして実際に定期的に使用する20のパターンを含む約100行になりました。そして、知っている通り、他の180のパターンを一度も恋しく思ったことはありません。ほんの少しも。
正規表現がほぼ私たちのAPIをダウンさせたとき
私が正規表現で引き起こした最悪の本番インシデントについてお話ししましょう。私たちはユーザー生成コンテンツを受け付けるAPIエンドポイントを持っていました—基本的にはユーザーが好きなことを書くことができるメモフィールドです。簡単なことですよね?
しかし、私たちはテキスト内のURLを検出して自動リンクしたかったのです。そこで、私はURLと偽陽性を避けると思われる巧妙な正規表現パターンを書きました。それは有効なプロトコルをチェックするための先読み、ドメイン名用の文字クラス、オプションのポート番号、パスセグメント、クエリパラメーター、フラグメント識別子を持っていました。それは美しく、包括的でした。ですが、壊滅的な間違いでした。
そのパターンはテストでは問題なく動作しました。さまざまなURLを試してみて、問題なく処理しました。金曜日の午後に本番環境にデプロイしたときは、かなり気分が良かったです。(そう、金曜日にはデプロイしないことを知っています。この教訓は辛い方法で学びました。)
1時間以内に、APIの応答時間は50msから30秒に増加しました。次にタイムアウトが発生し始めました。私たちの監視システムはクリスマスツリーのように点灯しました。ユーザーからの苦情があり、私の電話が鳴り続けました。ひどい状態でした。
原因は?ユーザーが偶然にも私の正規表現を引き起こすパターンが含まれた長いテキストを貼り付けていました。正規表現エンジンは、すべての組み合わせを試みようとしており、5,000文字の入力文字列でそれは数十億の試行を意味しました。各リクエストは、タイムアウトするまで30秒以上CPUコアを100%で占有していました。
私たちはすぐにロールバックし、私はそのパターンを書き直すために週末を過ごしました。新しいバージョンはよりシンプルで、「巧妙ではなく」繰り返しに対する明示的な制限を持っていました。それはすべての可能なURLフォーマットをキャッチしたわけではなく、実際に人々が使用する99.9%のURLをキャッチしました。そしてそれは秒ではなく、マイクロ秒で実行されました。
そのインシデントは私に重要なことを教えてくれました:正規表現の複雑さは資産ではなく負債です。パターンが凝っているほど、それは本番環境であなたを困らせる可能性が高くなります。一般的なケースを処理するシンプルなパターンは、すべてのエッジケースを処理する複雑なパターンよりもほとんど常に優れています。
実際に重要なことを分解する
数年にわたり正規表現を書き、私の誤りから学んだ結果、どのパターンを保持する価値があるかを決定するためのシンプルなフレームワークを開発しました。それは三つの基準に基づいています:
頻度:このパターンを月に少なくとも一度使用しますか?そうでなければ、必要なときにGoogleで検索できます。稀なユースケースのためのパターンを暗記したり維持したりする意味はありません。 信頼性:このパターンはさまざまな正規表現エンジンで一貫して機能しますか?JavaScript、Python、Goはいずれもわずかに異なる正規表現の実装を持っています。凝った機能に依存するパターンは移植性がないかもしれません。 パフォーマンス:このパターンは線形時間で実行されますか、それとも壊滅的なバックトラッキングを引き起こす可能性がありますか?私は入れ子の量指定子や重複したオプションについて神経質になってきました。これらの基準を使用すると、ほとんどのパターンは基準を満たしません。ISO 8601の日付をタイムゾーンオフセットと週番号で解析するための凝った正規表現?頻度テストに失敗します—年に2回必要になるだけで、そのときには調べられます。IBAN銀行口座番号を検証するパターン?信頼性テストに失敗します—非常に複雑であり、維持する自信がありません。入れ子のかっこをキャッチするための再帰的なパターン?パフォーマンステストに失敗します—バックトラッキングの悪夢が起こりそうです。
残っているのは、シンプルで迅速で、定期的に遭遇する問題を解決するパターンです。興味深いパターンではありません。賢く感じさせるパターンでもありません。でも、実際に重要なパターンです。
最高の正規表現パターンは、本番環境がダウンしていて、ユーザー入力が検証を壊している理由を理解しようとしているときに、6か月後の午前2時にでも理解できるものです。
大量削除を生き残った20のパターン
これは、実際に使用する正規表現パターンの完全なリストで、カテゴリごとに整理されています。これがサバイバーです—実際のプロジェクトで繰り返し使用され、その価値を証明したパターンです。
| パターン | 使用例 | 頻度 | 備考 |
|---|---|---|---|
/^\s+|\s+$/g |
空白のトリム | 毎日 | はい、私はtrim()が存在することを知っていますが、これはより多くの文脈で機能します |
/\s+/g |
空白の正規化 | 毎日 | 複数のスペースを単一のスペースに置き換えます |
/[^a-z0-9]/gi |
特殊文字の除去 | 毎週 | スラグ、ユーザー名などに使用 |
/^[a-z0-9_-]{3,16}$/i |
ユーザー名の検証 | 毎週 | 英数字、アンダースコア、ハイフン、3-16文字 |
/^.{8,}$/ |
パスワードの長さ | 毎週 | 少なくとも8文字、それだけです |
/.+@.+\..+/ |
メールの検証 | 毎週 | 99.9%のケースには十分です |
/^https?:\/\//i |
URLプロトコルのチェック | 毎週 | httpまたはhttpsのみ、特別なことはありません |
/\d+/g |
数字の抽出 | 毎日 | シンプルで迅速です |
/^\d+$/ |
数値入力の検証 | 毎週 | 数字だけ、他には何もありません |
/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/ |
日付フォーマットYYYY-MM-DD | 毎月 | フォーマットチェックのみ、検証ではありません |
/^#?([a-f0-9]{6}|[a-f0-9]{3})$/i |
16進数のカラーコード | 毎月 | ハッシュあり・なしどちらでも |
/\$\{([^}]+)\}/g |
テンプレート変数 | 毎月 | ${variable}パターンにマッチ |
//g |
HTMLコメント | 毎月 | コメントを削除するために使用 |
/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/ |
IPv4アドレス | 毎月 | 形式チェック、範囲検証ではありません |
/^[a-z0-9-]+$/i |
スラグの検証 | 毎週 | 小文字、数字、ハイフンのみ |
/\r?\n/g |
行の区切り | 毎週 | UnixとWindowsの両方を処理 |
/[<>]/g |
基本的な |