力を失うことなく読みやすい正規表現はありますか?


77

多くのプログラマーは、最近ではWebサービスの助けを借りて、またはより伝統的に対話型プロンプトで、または開発中の正規表現とテストケースのコレクションを持つ小さなスクリプトを書くことで、簡単な正規表現を作成する喜びを知っています。いずれの場合も、プロセスは反復的でかなり迅速です。必要なものが一致してキャプチャされ、不要なものが拒否されるまで、暗号のような文字列をハッキングし続けます。

単純な場合、結果はJava正規表現として次のようになります。

Pattern re = Pattern.compile(
  "^\\s*(?:(?:([\\d]+)\\s*:\\s*)?(?:([\\d]+)\\s*:\\s*))?([\\d]+)(?:\\s*[.,]\\s*([0-9]+))?\\s*$"
);

また、多くのプログラマーは、正規表現を編集したり、レガシーコードベースの正規表現をコード化するだけで苦労することも知っています。それを分割するために少し編集すると、上記の正規表現は正規表現にかなり精通している人にとってはまだ非常に簡単に理解でき、正規表現のベテランはそれが何をするかすぐに見るべきです(誰かが運動をしたい場合には、投稿の最後に答えて自分自身でそれを理解する)。

しかし、正規表現が真に書き込み専用になるために物事はそれほど複雑になる必要はなく、勤勉な文書(もちろん誰も書いているすべての複雑な正規表現についてもそうです)でさえ、正規表現の修正は困難な仕事。正規表現を慎重に単体テストしないと、非常に危険な作業になる可能性があります(ただし、もちろん、すべての複雑な正規表現(ポジティブとネガティブの両方)に対する包括的な単体テストがあります...)。

それで、長い話を簡単に言えば、正規表現の力を失うことなく、書き込み/読み取りソリューション/代替がありますか?上記の正規表現は、代替アプローチではどのようになりますか?どんな言語でも構いませんが、多言語ソリューションが最適ですが、正規表現は多言語です。


そして、以前の正規表現が行うことはこれです:formatの数字の文字列を解析し、1:2:3.4各数字をキャプチャします。スペースが許可され、3必要な場合のみです。


2
SOに関する関連事項
wim

24
正規表現の読み取り/編集は、キャプチャする内容がわかっていれば簡単です。「コメント」と呼ばれるほとんどの言語のこのめったに使用されない機能について聞いたことがあるかもしれません。それが何をするのかを説明する複雑な正規表現の上に置かなければ、後で代価を支払うことになります。また、コードレビュー。
TC1

2
これを実際に小さく分割せずにクリーンアップする2つのオプション。それらの有無は、言語によって異なります。(1)拡張行正規表現。正規表現内の空白は無視され(エスケープされない限り)、単一行のコメント形式が追加されるため、インデント、行間隔、コメントを含む論理的なチャンクに分割できます。(2)名前付きキャプチャグループ。各括弧に名前を付けることができます。これにより、自己文書化が追加され、一致のハッシュが自動的に入力されます。数値インデックス付きの一致配列または$ N変数よりも優れています。
ベン・リー

3
問題の一部は、正規表現言語自体と、荷物のように引きずられるデザインの悪い歴史的な選択です。正気の言語では、グループ化括弧は、構文解析ツリーを形成するための純粋に構文上のデバイスです。しかし、Unixに戻った正規表現の実装には、レジスタを部分式一致にバインドするというセマンティクスがあります。したがって、純粋なグループ化を実現するためだけに、より複雑で、い括弧が必要になります!
カズ

2
実際には実際的な答えではありませんが、正規表現の力は有限オートマトンの力とまったく同じであることに言及しておくと便利です。つまり、正規表現は、有限オートマトンによって検証および解析された文字列の同じクラスを検証/解析できます。したがって、人間が読むことのできる正規表現の表現は、おそらくグラフをすばやく作成できるはずです。ほとんどのテキストベースの言語は、それが本当に苦手だと思います。そのため、視覚ツールを使用しています。hackingoff.com/compilers/regular-expression-to-nfa-dfaを見て、インスピレーションを得てください。
-damix911

回答:


80

多くの人々が小さな部品から作曲することについて言及していますが、誰もまだ例を挙げていないので、ここに私のものがあります:

string number = "(\\d+)";
string unit = "(?:" + number + "\\s*:\\s*)";
string optionalDecimal = "(?:\\s*[.,]\\s*" + number + ")?";

Pattern re = Pattern.compile(
  "^\\s*(?:" + unit + "?" + unit + ")?" + number + optionalDecimal + "\\s*$"
);

最も読みやすいわけではありませんが、オリジナルよりもはっきりしているように感じます。

また、C#が有し@、それは文字通り(NOエスケープ文字)取るべきであることを示すために、文字列の先頭に追加することができ、オペレータが、そうnumberなるであろう@"([\d]+)";


ちょうど今、両方[\\d]+とどのようにある[0-9]+べきかに気づきました\\d+(まあ、[0-9]+より読みやすいと思うかもしれません)。質問を編集するつもりはありませんが、この回答を修正することをお勧めします。
ハイド

@hyde-良いキャッチ。技術的にはまったく同じものではありません- \d他の番号付けシステム(中国語、アラビア語など)でも数字と見なされるものはすべて[0-9]一致しますが、標準の数字は一致します。\\dただし、標準化を行い、optionalDecimalパターンに組み込みました。
ボブソン

42

正規表現を文書化する鍵は、それを文書化することです。ラインノイズのように見えるものを投げて、そのままにしておきます。

perl/xで、正規表現の末尾の演算子は空白を抑制し、正規表現を文書化できるようにします。

上記の正規表現は次のようになります。

$re = qr/
  ^\s*
  (?:
    (?:       
      ([\d]+)\s*:\s*
    )?
    (?:
      ([\d]+)\s*:\s*
    )
  )?
  ([\d]+)
  (?:
    \s*[.,]\s*([\d]+)
  )?
  \s*$
/x;

はい。垂直方向の空白を少し消費しますが、読みやすさをあまり犠牲にすることなく短くすることができます。

そして、以前の正規表現が行うことはこれです:1:2:3.4の形式で数字の文字列を解析し、各数字をキャプチャします。スペースが許可され、3つだけが必要です。

この正規表現を見ると、それがどのように機能するか(そして機能しない)を見ることができます。この場合、この正規表現は文字列と一致します1

他の言語でも同様のアプローチをとることができます。python re.VERBOSEオプションはそこで機能します。

Perl6(上記の例はperl5の場合)は、PCREよりも強力な構造につながるルールの概念でこれをさらに進めます(通常の拡張された通常の文法よりも他の文法(コンテキストに依存せず、文脈依存)へのアクセスを提供します)。

Java(この例の作成元)では、文字列の連結を使用して正規表現を作成できます。

Pattern re = Pattern.compile(
  "^\\s*"+
  "(?:"+
    "(?:"+
      "([\\d]+)\\s*:\\s*"+  // Capture group #1
    ")?"+
    "(?:"+
      "([\\d]+)\\s*:\\s*"+  // Capture group #2
    ")"+
  ")?"+ // First groups match 0 or 1 times
  "([\\d]+)"+ // Capture group #3
  "(?:\\s*[.,]\\s*([0-9]+))?"+ // Capture group #4 (0 or 1 times)
  "\\s*$"
);

確かに、これにより"文字列にさらに多くの混乱が生じる可能性があり、読みやすく(特にほとんどのIDEで構文の強調表示を使用)、文書化できます。

重要なのは、正規表現がしばしば陥るパワーと「一度だけ書く」性質を認識することです。正規表現が明確で理解可能なままであるように、これを防御的に回避するコードを書くことが重要です。わかりやすくするためにJavaコードをフォーマットします-言語がそうするオプションを提供する場合、正規表現は変わりません。


13
「文書化」と「改行の追加」には大きな違いがあります。

4
@JonofAllTradesコードを読み取り可能にすることは、何でも最初のステップです。改行を追加すると、REのそのサブセットのコメントを同じ行に追加することもできます(正規表現テキストの1本の長い行で行うのがより困難なこと)。

2
@JonofAllTrades、私はかなり強く反対します。「ドキュメント化」と「改行の追加」は、どちらも同じ目的を果たし、コードを理解しやすくするという点でそれほど違いはありません。また、フォーマットが不適切なコードの場合、「改行の追加」は、ドキュメントを追加するよりもはるかにその目的に役立ちます。
ベン・リー

2
改行の追加は開始点ですが、それは仕事の約10%です。他の回答では、より詳細な情報が提供されます。

26

一部の言語およびライブラリが提供する「詳細」モードは、これらの懸念に対する答えの1つです。このモードでは、正規表現文字列の空白が取り除かれ(したがって、使用する必要があります\s)、コメントが可能になります。デフォルトでこれをサポートするPythonの短い例を次に示します。

email_regex = re.compile(r"""
    ([\w\.\+]+) # username (captured)
    @
    \w+         # minimal viable domain part
    (?:\.w+)    # rest of the domain, after first dot
""", re.VERBOSE)

そうでない言語では、詳細モードから「通常」モードへのトランスレーターの実装は簡単なタスクです。正規表現の読みやすさを心配しているなら、おそらくこの時間の投資をかなり簡単に正当化するでしょう。


15

正規表現を使用するすべての言語では、より単純なブロックから正規表現を作成して読みやすくすることができ、例よりも複雑な(または複雑な)ものがある場合は、必ずこのオプションを利用する必要があります。Javaや他の多くの言語の特定の問題は、正規表現を「ファーストクラス」の市民として扱わず、文字列リテラルを介して言語に忍び込むことを要求することです。これは、実際には正規表現構文の一部ではなく、読みにくくする多くの引用符とバックスラッシュを意味します。また、独自のミニ言語とインタープリターを効果的に定義しない限り、それよりもはるかに読みやすくなることはできません。

正規表現を統合するためのプロトタイプのより良い方法は、もちろん、Perlで、その空白オプションと正規表現引用演算子がありました。Perl 6は、正規表現をパーツか​​ら実際の再帰的な文法に構築するという概念を拡張します。言語は適時の船を見逃したかもしれませんが、正規表現のサポートはThe Good Stuff(tm)でした。


1
答えの冒頭で言及した「より単純なブロック」とは、単なる文字列の連結を意味しますか、それともより高度なものを意味しますか?
ハイド

7
副次式を短い文字列リテラルとして定義し、意味のある名前でローカル変数に割り当ててから、連結することを意味しました。名前は、レイアウトの改善よりも読みやすさにとって重要です。
キリアンフォス

11

Expressoを使用したい:http : //www.ultrapico.com/Expresso.htm

この無料のアプリケーションには、時間の経過とともに役立つと思われる次の機能があります。

  • 正規表現をコピーして貼り付けるだけで、アプリケーションはそれを解析します
  • 正規表現を作成したら、アプリケーションから直接テストできます(アプリケーションは、キャプチャ、置換のリストを提供します...)
  • テストしたら、C#コードを生成して実装します(コードには正規表現に関する説明が含まれていることに注意してください)。

たとえば、送信した正規表現では、次のようになります。 最初に与えられた正規表現のサンプル画面

もちろん、試してみると、それを説明する千の言葉に値します。また、このアプリケーションの編集者と何らかの形で関連していることに注意してください。


4
このことについてさらに詳しく説明してもらえますか?質問に対してどのように、なぜ答えるのですか?「リンクのみの答えは、」スタック所ではない、非常に歓迎されている
ブヨ

5
@gnatそれについてすみません。あなたは絶対に正しいです。編集した回答がより多くの洞察を提供してくれることを願っています。
E.ジェップ

9

いくつかの点で、BNFのような文法を使用するだけで役立つ場合があります。これらは、正規表現よりも読みやすくなります。次に、GoldParser Builderなどのツールを使用して、文法をパーサーに変換し、面倒な作業を行うことができます。

BNF、EBNFなどの文法は、複雑な正規表現よりも読みやすく、作成しやすい場合があります。GOLDはそのようなことのツールの1つです。

以下のc2 wikiリンクには、グーグルで検索できる代替のリストがあり、それらに関するいくつかの議論が含まれています。これは基本的に、文法エンジンの推奨事項を補完する「参照」リンクです。

正規表現の代替

「代替」とは「構文が異なる意味的に同等の機能」を意味しますが、少なくともRegularExpressionsに対する/との代替手段があります。

  • 基本的な正規表現
  • 「拡張」正規表現
  • Perl互換の正規表現
  • ...および他の多くのバリアント...
  • SNOBOLスタイルのRE構文(SnobolLanguage、IconLanguage)
  • SRE構文(REはEssExpressionsとして)
  • 異なるFSMシンタックス
  • 有限状態交差文法(非常に表現力豊か)
  • OMetaLanguageおよびLuaLanguage(http://www.inf.puc-rio.br/~roberto/lpeg/lpeg.html)のようなParsingExpressionGrammars
  • RebolLanguageの解析モード
  • 確率ベースの解析...

このリンクが何をするのか、何が良いのかをもっと説明していただけますか?「リンクのみの回答」はStack Exchangeでは歓迎されません
-gnat

1
プログラマー、Nick Pへようこそ。downvote/ rは無視してください。ただし、@ gnatがリンクしているメタのページを読んでください。
クリストファーレッテ

@ Christoffer Lette返信に感謝します。今後の投稿でこれを念頭に置いてください。@ gnat Paulo Scardineのコメントは、私の投稿の意図を反映しています。BNF、EBNFなどの文法は、複雑な正規表現よりも読みやすく、作成しやすい場合があります。GOLDはそのようなことのツールの1つです。c2リンクには、グーグルで検索できる代替のリストがあり、それらに関するいくつかの議論が含まれています。これは基本的に、文法エンジンの推奨事項を補完する「参照」リンクでした。
ニックP

6

これは古い質問であり、私は言語表現について言及していなかったので、将来の求職者のためにもその情報をここに追加すると思いました。言語表現は、正規表現のシンボルの意味を学ぶ必要なく、正規表現を人間が理解できるように特別に設計されました。次の例を参照してください。これは、あなたが求めていることを最もうまくやると思う。

// Create an example of how to test for correctly formed URLs
var tester = VerEx()
    .startOfLine()
    .then('http')
    .maybe('s')
    .then('://')
    .maybe('www.')
    .anythingBut(' ')
    .endOfLine();

// Create an example URL
var testMe = 'https://www.google.com';

// Use RegExp object's native test() function
if (tester.test(testMe)) {
    alert('We have a correct URL '); // This output will fire}
} else {
    alert('The URL is incorrect');
}

console.log(tester); // Outputs the actual expression used: /^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$/

この例はJavaScript用です。多くのプログラミング言語でこのライブラリを見つけることができます


2
これはすごい!
ジェレミートンプソン

3

最も簡単な方法は、まだ正規表現を使用することですが、たとえばhttp://www.martinfowler.com/bliki/ComposedRegex.htmlのようなわかりやすい名前で、より単純な表現を作成して式を作成します(これは文字列concatからです)

ただし、代替手段として、パーサーコンビネータライブラリ(http://jparsec.codehaus.org/など)を使用することもできます。これにより、完全な再帰的パーサーが得られます。繰り返しますが、ここでの真の力は構成(今回は機能構成)にあります。


3

logstashのgrok表現に言及する価値があると思いました。Grokは、短い解析式から長い解析式を作成するという考えに基づいています。これらのビルディングブロックを簡単にテストでき、一般的に使用される 100以上のパターンがあらかじめパッケージ化されています。これらのパターン以外に、すべての正規表現構文を使用できます。

grokで表現された上記のパターンは次のとおりです(デバッガーアプリでテストしましたが、失敗した可能性があります)。

"(( *%{NUMBER:a} *:)? *%{NUMBER:b} *:)? *%{NUMBER:c} *(. *%{NUMBER:d} *)?"

オプションのパーツとスペースは、通常よりも少しugいように見えますが、ここでも、他の場合でも、grokを使用することで人生をより良くすることができます。


2

F#には、FsVerbalExpressionsモジュールがあります。言語表現から正規表現を作成できます。また、URLなどの事前作成済みの正規表現もいくつかあります。

この構文の例の1つは次のとおりです。

let groupName =  "GroupNumber"

VerbEx()
|> add "COD"
|> beginCaptureNamed groupName
|> any "0-9"
|> repeatPrevious 3
|> endCapture
|> then' "END"
|> capture "COD123END" groupName
|> printfn "%s"

// 123

F#構文に慣れていない場合、groupNameは文字列「GroupNumber」です。

次に、「COD(?<GroupNumber> [0-9] {3})END」として構築する言語表現(VerbEx)を作成します。次に、文字列「COD123END」でテストし、名前付きキャプチャグループ「GroupNumber」を取得します。これは123になります。

正直に言って、通常の正規表現は理解しやすいと思います。


-2

まず、単に機能するコードが悪いコードであることを理解してください。優れたコードは、発生したエラーを正確に報告する必要もあります。

たとえば、あるユーザーのアカウントから別のユーザーのアカウントに現金を転送する関数を作成している場合、"worked or failed"ブール値を返すだけではありません。これは、発信者に何が問題なのかを知らず、発信者がユーザーに適切に通知できないためです。代わりに、エラーコードのセット(または例外のセット)がある可能性があります:宛先アカウントが見つからなかった、ソースアカウントの資金が不足していた、許可が拒否された、データベースに接続できなかった、負荷が大きすぎる(後で再試行)など。

ここで、「1:2:3.4の形式で数値の文字列を解析する」例について考えてください。正規表現が行うことは、適切なフィードバックをユーザーに提示できない「合格/不合格」を報告することです(このフィードバックがログのエラーメッセージであるか、エラーがユーザータイプ、またはその他)。どのような種類のエラーが適切に説明できませんか?最初の数字の文字が正しくない、最初の数字が大きすぎる、最初の数字の後にコロンがない、など

「単に機能する悪いコード」を「適切に記述的なエラーを提供する良いコード」に変換するには、正規表現を多くの小さな正規表現に分割する必要があります)。

コードを読み取り可能/保守可能にすることは、コードを改善することの偶然の結果です。


6
おそらく良い仮定ではありません。私の理由は、A)これは質問に対応していない(読みやすくする方法ですか?)、B)正規表現の一致合格/不合格であり、失敗した理由を正確に言うことができるポイントまで分解すると、多くのパワーとスピードを失い、複雑さを増す。C)マッチが失敗する可能性さえあるという質問からは何の兆候もありません。それは単にRegexを読みやすくすることに関する質問です。入ってくるデータを制御したり、事前に検証したりすると、そのデータが有効であると想定できます。
ボブソン

A)小さく分割すると、読みやすくなります(改善した結果)。C)未知の/検証されていない文字列がソフトウェアの一部に入ると、健全な開発者はその時点で解析し(エラー報告付き)、データを再解析を必要としない形式に変換します-その後、正規表現は必要ありません。B)不正なコードにのみ適用されるナンセンスです(ポイントAおよびCを参照)。
ブレンダン

Cから:これ彼の検証ロジックである場合どうなりますか?OPのコードは、まさにあなたが提案しているものです-入力を検証し、入力が有効でない場合は報告し、使用可能な形式に変換します(キャプチャを介して)。表現そのものです。正規表現以外の構文解析をどのように提案しますか?同じ結果を達成するサンプルコードを追加する場合は、下票を削除します。
ボブソン

これが「C:検証中(エラー報告あり)」の場合、エラー報告が悪いため、コードが不正です。失敗した場合; それは、文字列がNULLだったのか、最初の数字の桁数が多すぎるのか、最初の区切り文字がそうではなかったの:ですか?問題が何であるかをユーザーに伝えるにはあまりにも愚かなエラーメッセージ( "ERROR")が1つしかなかったコンパイラを想像してください。ここで、「バッドメールアドレス」などと同じくらい愚かで表示される何千ものWebサイトを想像してください。
ブレンダン

また、完全に訓練されていないユーザーから次のようなバグレポートを受け取った半熟のヘルプデスクオペレーターを想像してください。ソフトウェアが動作を停止しました。 '(2番目の数字の後にコロンが必要です) "
ブレンダン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.