この記事の提案は主に正規表現の読みやすさに焦点を当てており、開発中にこれらの習慣を身につければ、設計と式の構造をより明確に考慮できるようになり、バグやコードのメンテナンスが軽減されます。あなた自身がこのコードの管理者です。実際に正規表現を使用する際のこれらの経験を自分自身で確認し、注意を払うことができます。
正規表現は、書くのが難しく、読み取りが難しく、保守も困難です。これらの問題は、正規表現のパフォーマンスと機能によって引き起こされます。各メタキャラクターの機能とニュアンスの組み合わせにより、知的トリックに頼らずにコードを解釈することは不可能になります。
多くのツールには正規表現の読み取りと書き込みを容易にする機能が含まれていますが、それらは非常に非慣用的でもあります。多くのプログラマにとって、正規表現を書くことは魔法のような技術です。彼らは自分が知っている特徴に固執し、絶対的な楽観主義の態度をとります。この記事で説明した 5 つの習慣を積極的に採用すれば、試行錯誤に耐えられる正規表現を設計できるようになります。
この記事ではコード例として Perl、PHP、および Python 言語を使用しますが、この記事のアドバイスはほぼすべての置換式 (正規表現) の実装に当てはまります。
1. スペースとコメントを使用する
ほとんどのプログラマーにとって、正規表現環境でスペースとインデントを使用することは問題ではありません。これを行わないと、同僚や素人からも笑われるでしょう。コードを 1 行に詰め込むと、読み書き、保守が困難になることは、ほとんどの人が知っています。正規表現との違いは何ですか?
ほとんどの置換式ツールには拡張空白機能があり、プログラマーは正規表現を複数行に拡張し、各行の末尾にコメントを追加できます。なぜ少数のプログラマだけがこの機能を利用するのでしょうか? Perl 6 の正規表現は、デフォルトでスペース拡張パターンを使用します。言語によってデフォルトでスペースが拡張されるのではなく、自分でスペースを活用してください。
拡張空白について覚えておくべきコツの 1 つは、拡張空白を無視するように正規表現エンジンに指示することです。この方法では、スペースを一致させる必要がある場合は、明示的に指定する必要があります。
Perl 言語では、正規表現の最後に x を追加すると、「m/foo bar/」は次の形式になります:
m/
ふー
バー
/x
PHP 言語の場合、正規表現の最後に x を追加すると、「"/foo bar/"」は次の形式になります:
"/
ふー
バー
/x"
Python 言語では、パターン変更パラメーター "re.VERBOSE" を渡して、次のようにコンパイルされた関数を取得します。
pattern = r'''
ふー
バー
「」
regex = re.compile(pattern, re.VERBOSE) が
より複雑な正規表現を処理する場合、スペースとコメントがより重要になります。米国の電話番号を照合するために次の正規表現が使用されているとします:
(?d{3})?d{3}[-.]d{4}
この正規表現は、次のような電話番号と照合します。 「( 314)555-4000」、この正規表現は「314-555-4000」または「555-4000」のどちらに一致すると思いますか?答えは、どちらも一致しないということです。このようなコード行を記述すると、欠点と設計結果自体が隠蔽されます。電話の市外局番は必須ですが、正規表現には市外局番とプレフィックス間の区切り記号がありません。
このコード行を複数の行に分割してコメントを追加すると、欠点が明らかになり、変更が容易になります。
Perl 言語では、次の形式にする必要があります:
/
(? # 括弧は省略可能
d{3} # 必須の電話市外局番
)? # オプションの括弧
[-s.]? # 区切り文字にはダッシュ、スペース、またはピリオドを使用できます。
d{3} # 3 桁の接頭辞
[-.] # 別の区切り文字
d{4} # 4 桁の電話番号
/x
書き換えられた正規表現には市外局番の後にオプションの区切り文字が付けられるようになり、「314-555-4000」と一致するようになりますが、市外局番は依然として必要です。電話の市外局番をオプションにする必要がある別のプログラマーは、市外局番がオプションではなくなったことをすぐに確認でき、小さな変更で問題を解決できる可能性があります。
2.
テストの作成には 3 つのレベルがあります。各レベルでコードの信頼性が高まります。まず、どのコードを一致させる必要があるか、不一致を処理できるかどうかを慎重に考える必要があります。次に、データ インスタンスを使用して正規表現をテストする必要があります。最後に、正式にテストパネルに合格する必要があります。
何を一致させるかを決定することは、実際には、間違った結果の一致と、正しい結果の欠落との間のバランスを見つけることになります。正規表現が厳密すぎると、一部の正しい一致が失われます。逆に緩すぎると、不正確な一致が生成されます。正規表現が実際のコードにリリースされると、両方に気付かない場合があります。上記の電話番号の例を考えてみましょう。これは「800-555-4000 = -5355」に一致します。間違った一致を検出するのは実際には難しいため、事前に計画を立ててよくテストすることが重要です。
電話番号の例を続けると、Web フォームで電話番号を確認する場合は、どの形式の 10 桁の番号でも問題ない可能性があります。ただし、大量のテキストから電話番号を分離したい場合は、要件を満たさない誤った一致を慎重に除外する必要がある場合があります。
照合するデータについて考えるときは、いくつかのケース シナリオを書き留めます。ケースのシナリオに対して正規表現をテストするコードを作成します。複雑な正規表現の場合は、次のような特定の形式の小さなプログラムを作成してテストするのが最善です。
Perl 言語の場合:
#!/usr/bin/perl
my @tests = ( "314-555-4000",
"800-555-4400"、
"(314)555-4000",
"314.555.4000",
"555-4000"、
"aasdklfjklas",
「1234-123-12345」
);
foreach 私の $test (@tests) {
if ( $test =~ m/
(? # 括弧は省略可能
d{3} # 必須の電話市外局番
)? # オプションの括弧
[-s.]? # 区切り文字にはダッシュ、スペース、またはピリオドを使用できます。
d{3} # 3 桁の接頭辞
[-s.] # 別の区切り文字
d{4} # 4 桁の電話番号
/x ) {
print "$test に一致しましたn";
}
それ以外 {
print "$test の一致に失敗しましたn";
}
場合
:
<?php
$tests = array( "314-555-4000",
"800-555-4400",
"(314)555-4000",
"314.555.4000",
"555-4000"、
"aasdklfjklas",
"1234-123-12345" );
$regex = "/
(? # 括弧は省略可能
d{3} # 必須の電話市外局番
)? # オプションの括弧
[-s.]? # 区切り文字にはダッシュ、スペース、またはピリオドを使用できます。
d{3} # 3 桁の接頭辞
[-s.] # 別の区切り文字
d{4} # 4 桁の電話番号
/x";
foreach ($tests as $test) {
if (preg_match($regex, $test)) {
echo "$test に一致しました
;";
}
それ以外 {
echo "$test の一致に失敗しました
;";
}
}
?>;
Python 言語の場合:
import re
testing = ["314-555-4000",
"800-555-4400"、
"(314)555-4000",
"314.555.4000",
"555-4000"、
"aasdklfjklas",
「1234-123-12345」
]
パターン = r''
(? # 括弧は省略可能
d{3} # 必須の電話市外局番
)? # オプションの括弧
[-s.]? # 区切り文字にはダッシュ、スペース、またはピリオドを使用できます。
d{3} # 3 桁の接頭辞
[-s.] # 別の区切り文字
d{4} # 4 桁の電話番号
'''
テスト内のテスト用の regex = re.compile( pattern, re.VERBOSE ):
regex.match(テスト)の場合:
print "一致しました"、テスト、"n"
それ以外:
print "Failed match on", test, "n"
テスト コードを実行すると、別の問題が明らかになります。それは、「1234-123-12345」と一致します。
理論的には、アプリケーション全体のすべてのテストをテスト チームに統合する必要があります。まだテスト グループを持っていない場合でも、正規表現テストはテスト グループの良い基礎となるため、今がテスト グループを始める良い機会です。まだ正規表現を作成する適切な時期ではない場合でも、変更するたびに正規表現を実行してテストする必要があります。ここで少し時間を費やせば、多くのトラブルを避けることができます。
3. グループ交互演算
交互演算記号 ( ) は優先度が低いため、プログラマの意図以上に交互に動作することがよくあります。たとえば、テキストから電子メール アドレスを抽出するための正規表現は次のようになります。
^CC: To:(.*)
上記の試みは間違っていますが、このバグは気づかれないことがよくあります。上記のコードの目的は、「CC:」または「To:」で始まるテキストを検索し、この行の末尾にある電子メール アドレスを抽出することです。
残念ながら、「To:」が行の途中にある場合、この正規表現は「CC:」で始まる行をキャプチャせず、代わりにいくつかのランダムなテキストを抽出します。率直に言って、正規表現は「CC:」で始まる行に一致しますが何もキャプチャしないか、「To:」を含む行に一致しますが行の残りの部分をキャプチャします。通常、この正規表現は多数の電子メール アドレスを取得するため、誰もバグに気づきません。
実際の意図に応えたい場合は、括弧を追加して明確にする必要があります。正規表現は次のとおりです。
(^CC:) (To:(.*))
本当の意図が " で始まるテキストをキャプチャすることである場合。行の残りを CC:" または "To:" とすると、正しい正規表現は次のようになります:
^(CC: To:)(.*)
これはよくある不完全一致のバグですが、グループ化する習慣を付けていれば回避できます。交互操作の場合、このエラーが発生します。
4. 緩い量指定子を使用する
多くのプログラマーは、式を書きやすく理解しやすくするにもかかわらず、「*?」、「+?」、「??」などの緩い量指定子の使用を避けます。
緩和された量指定子は、できるだけ少ないテキストと一致するため、正確な一致が成功しやすくなります。 「foo(.*?)bar」と書いた場合、量指定子は最後ではなく、最初に「bar」に遭遇したときにマッチングを停止します。これは、「foo###bar++bar」から「###」をキャプチャする場合に重要です。厳密な数量指定子は「###bar++ +」をキャプチャします。 ;)、これは多くの問題を引き起こします。緩和された量指定子を使用すると、文字タイプの組み立てにほとんど時間を費やさずに新しい正規表現を生成できます。
緩和された量指定子は、テキストをキャプチャするコンテキストの構造がわかっている場合に非常に役立ちます。
5. 利用可能な区切り文字を使用します。Perl
言語と PHP 言語では、正規表現の先頭と末尾を示すために左スラッシュ (/) が使用されます。Python 言語では、先頭と末尾を示すために一連の引用符が使用されます。 Perl や PHP で左スラッシュを使用する場合は、式内でスラッシュを使用しないようにする必要があります。Python で引用符を使用する場合は、バックスラッシュ () を使用しないようにします。別の区切り文字または引用符を選択すると、正規表現の半分を省略できます。これにより、式が読みやすくなり、記号の回避を忘れることによって引き起こされる潜在的なバグが軽減されます。
Perl および PHP 言語では、数値以外の文字やスペース文字を区切り文字として使用できます。新しい区切り文字に切り替えると、URL または HTML タグ (「http://」や「<br/>;」など) を照合するときに左スラッシュを見逃すことを防ぐことができます。
たとえば、「/http://(S)*/」は「#http://(S)*#」と書くことができます。
一般的な区切り文字は「#」、「!」、「」です。角括弧、山括弧、または中括弧を使用する場合は、それらを一致させてください。一般的な区切り文字の例をいくつか示します。
#…# !…! {…} s … … (Perl のみ) s[…][…] (Perl のみ) s<…>;/…/ (Perl のみ)
Python では、正規表現は最初に文字列として扱われます。区切り文字として引用符を使用すると、すべてのバックスラッシュが欠落します。ただし、「r''」文字列を使用すると、この問題を回避できます。 「re.VERBOSE」オプションに 3 つの連続した一重引用符を使用すると、改行を含めることができます。たとえば、regex = "( file://w+)(//d +)" は次の形式で記述できます:
regex = r'''
(w+)
(d+)
「」