9

スクリプトの1つでPerl::Criticを実行すると、次のメッセージが表示されます。

Regular expression without "/x" flag at line 21, column 26. See page 236 of PBP.

ここでポリシー情報を調べましたが、拡張モードで正規表現を作成すると、コードを見ている人に役立つことを理解しています。

ただし、/xフラグを使用するようにコードを変換する方法として立ち往生しています。

CPANの例:

# Match a single-quoted string efficiently...

m{'[^\\']*(?:\\.[^\\']*)*'};  #Huh?

# Same thing with extended format...

m{
    '           # an opening single quote
    [^\\']      # any non-special chars (i.e. not backslash or single quote)
    (?:         # then all of...
        \\ .    #    any explicitly backslashed char
        [^\\']* #    followed by an non-special chars
    )*          # ...repeated zero or more times
    '           # a closing single quote
}x;

これは、正規表現だけを見る場合に意味があります。

私のコード:

if ($line =~ /^\s*package\s+(\S+);/ ) {

ifステートメント内で拡張正規表現を使用する方法が正確にはわかりません。私はそれをこのように書くことができます:

    if (
        $line =~ /
        ^\s*    # starting with zero or more spaces
        package
        \s+     # at least one space
        (\S+)   # capture any non-space characters
        ;       # ending in a semi-colon
        /x
      )
    {

そして、これは機能しますが、これはオリジナルよりもほとんど読みにくいと思います。これを書くためのより良い方法(またはベストプラクティスの方法)はありますか?qr//を使って変数を作成できると思います。

私はこの特定の正規表現を書き直すことについてのアドバイスを本当に探していません(ただし、改善できる場合はアドバイスを受け取ります)-ifステートメント内で正規表現を拡張する方法についてのアドバイスをもっと探しています。

Perl :: Criticは単なるガイドラインですが、それに従うとよいでしょう。

前もって感謝します!

編集: いくつかの回答を受け取った後、コメント付きの正規表現を複数行にする必要は必ずしもないことがわかりました。基本的な正規表現を理解している人は、私の例が何をしているかを理解できるはずです。私が追加したコメントは、おそらく少し不必要で冗長でした。拡張正規表現フラグを使用するというアイデアは気に入っていますが、正規表現の各部分をもう少し明確にするために、正規表現にスペースを埋め込みます。すべての入力をありがとう!

4

5 に答える 5

12

コードの内容を示すコメントは絶対に書かないでください。コメントは、コードが何を言っているのかを教えてくれるはずです。この怪物を見てください。コメントがないと、何が起こっているのかを確認するのは非常に困難ですが、コメントは、が一致しようとしているのかを明確にします。

require 5.010;
my $sep         = qr{ [/.-] }x;               #allowed separators    
my $any_century = qr/ 1[6-9] | [2-9][0-9] /x; #match the century 
my $any_decade  = qr/ [0-9]{2} /x;            #match any decade or 2 digit year
my $any_year    = qr/ $any_century? $any_decade /x; #match a 2 or 4 digit year

#match the 1st through 28th for any month of any year
my $start_of_month = qr/
    (?:                         #match
        0?[1-9] |               #Jan - Sep or
        1[0-2]                  #Oct - Dec
    )
    ($sep)                      #the separator
    (?: 
        0?[1-9] |               # 1st -  9th or
        1[0-9]  |               #10th - 19th or
        2[0-8]                  #20th - 28th
    )
    \g{-1}                      #and the separator again
/x;

#match 28th - 31st for any month but Feb for any year
my $end_of_month = qr/
    (?:
        (?: 0?[13578] | 1[02] ) #match Jan, Mar, May, Jul, Aug, Oct, Dec
        ($sep)                  #the separator
        31                      #the 31st
        \g{-1}                  #and the separator again
        |                       #or
        (?: 0?[13-9] | 1[0-2] ) #match all months but Feb
        ($sep)                  #the separator
        (?:29|30)               #the 29th or the 30th
        \g{-1}                  #and the separator again
    )
/x;

#match any non-leap year date and the first part of Feb in leap years
my $non_leap_year = qr/ (?: $start_of_month | $end_of_month ) $any_year/x;

#match 29th of Feb in leap years
#BUG: 00 is treated as a non leap year
#even though 2000, 2400, etc are leap years
my $feb_in_leap = qr/
    0?2                         #match Feb
    ($sep)                      #the separtor
    29                          #the 29th
    \g{-1}                      #the separator again
    (?:
        $any_century?           #any century
        (?:                     #and decades divisible by 4 but not 100
            0[48]       | 
            [2468][048] |
            [13579][26]
        )
        |
        (?:                     #or match centuries that are divisible by 4
            16          | 
            [2468][048] |
            [3579][26]
        )
        00                      
    )
/x;

my $any_date  = qr/$non_leap_year|$feb_in_leap/;
my $only_date = qr/^$any_date$/;
于 2009-06-12T16:51:52.220 に答える
11

まあ、私はあなたがこれで垂直スクリーンの不動産を無駄にするべきではないと本当に思います。一方、このパターンを複数の行に書き込む場合は、中かっこを使用してパターンをインデントします。

if ($line =~ m{
        \A \s*
        package
        \s+
        (\S+)
        \s* ;
    }x 
) {

私見、次のバージョンは完全に問題ありません:

if ( $line =~ m{ \A \s* package \s+ (\S+) \s* ; }x  ) {

の利益を得るという点でm//x

この場合、トリッキーなことは何もしていないので、コメントは完全に不要です。セミコロンの前に追加\s*したのは、パッケージ名とは別にセミコロンを設定することがあり、それによって一致が失われることがないためです。

于 2009-06-12T16:00:53.200 に答える
8

そのような追加情報によって付加価値がもたらされるのは、ほとんどあなたの呼びかけです。

正しい場合もありますが、何が起こっているのかを説明するものは何も追加されず、コードが乱雑に見えるだけですが、複雑な正規表現の場合、xフラグは恩恵になる可能性があります。

実際、追加情報の付加価値に関するこの「電話をかける」ことは非常に難しい場合があります。

美しくフォーマットされたコメントが維持されておらず、コードが実行していることから逸脱しているレガシーコードを何度見たのか思い出せません。実際、経験が浅いときは、コードの一部に関連付けられたコメントが古く、維持されていなかったため、完全に間違った道をたどりました。

編集:ある意味では、CPANの例はそれほど有用ではありません。xフラグを使用してコメントを追加し、複雑な正規表現を説明する場合、正規表現の「ビット」自体を説明するだけでなく、正規表現が一致させようとしているコンポーネントを説明する傾向があります。たとえば、次のように記述します。

  • 英国の郵便番号の最初のコンポーネント(エリアと地区)、または
  • 英国の国際市外局番、または
  • 英国の携帯電話番号。

それは私にもっと教えてくれます

  • 1文字または2文字の後に数字が続く、オプションで文字が続く、または
  • 2つの4桁を一緒に、または
  • ゼロ、10進数の4桁、ダッシュ、10進数の6桁が続きます。

この場合、正規表現のコメントは省略したいと思います。あなたの腸の感覚は正しいです!

于 2009-06-12T16:02:34.820 に答える
6

このトピックは、正規表現を作成する別の方法に関するものです。変数やコメントを使用せずに複雑な正規表現を作成する方法もありますが、それでも便利です。

正規表現を検証するChasOwensの日付を、Perl-5.10で利用可能な新しい宣言型フォームにリフローしました。これには多くの利点があります。

  • 正規表現のトークンは再利用可能です
  • 後で正規表現を印刷すると、ロジックツリー全体が表示されます。

みんなの魚のやかんではないかもしれませんが、日付の検証などの非常に複雑なものには便利です(ps:現実の世界では、日付のもののモジュールを使用してください、DIYはしないでください、これは学ぶための単なる例ですから )

#!/usr/bin/perl 
use strict;
use warnings;
require 5.010;

#match the 1st through 28th for any month of any year
my $date_syntax = qr{
    (?(DEFINE)
        (?<century>
            ( 1[6-9] | [2-9][0-9] )
        )
        (?<decade>
            [0-9]{2} (?!\d)
        )
        (?<year>
            (?&century)? (?&decade)(?!\d)
        )
        (?<leapdecade> (
            0[48]       | 
            [2468][048] |
            [13579][26]
            )(?!\d)
        )
        (?<leapcentury> (
            16          | 
            [2468][048] |
            [3579][26]
            )
        )   
        (?<leapyear>
            (?&century)?(?&leapdecade)(?!\d)
            |
            (?&leapcentury)00(?!\d)
        )
        (?<monthnumber>      ( 0?[1-9] | 1[0-2] )(?!\d)                  )
        (?<shortmonthnumber> ( 0?[469] | 11     )(?!\d)                  )
        (?<longmonthnumber>  ( 0?[13578] | 1[02] )(?!\d)                 )
        (?<nonfebmonth>      ( 0?[13-9] | 1[0-2] )(?!\d)                 )
        (?<febmonth>         ( 0?2 )(?!\d)                               )
        (?<twentyeightdays>  ( 0?[1-9] | 1[0-9] | 2[0-8] )(?!\d)         )
        (?<twentyninedays>   ( (?&twentyeightdays) | 29 )(?!\d)          )
        (?<thirtydays>       ( (?&twentyeightdays) | 29 | 30 )(?!\d)     )
        (?<thirtyonedays>    ( (?&twentyeightdays) | 29 | 30 | 31 )(?!\d))
        (?<separator>        [/.-]                              )               #/ markdown syntax highlighter fix
        (?<ymd>
            (?&leapyear) (?&separator) (?&febmonth) (?&separator) (?&twentyninedays) (?!\d)
            |
            (?&year) (?&separator) (?&longmonthnumber) (?&separator) (?&thirtyonedays) (?!\d)
            |
            (?&year) (?&separator) (?&shortmonthnumber) (?&separator) (?&thirtydays) (?!\d)
            |
            (?&year) (?&separator) (?&febmonth) (?&separator) (?&twentyeightdays) (?!\d)
        )
        (?<mdy>
            (?&febmonth) (?&separator) (?&twentyninedays) (?&separator) (?&leapyear)  (?!\d)
            |
            (?&longmonthnumber) (?&separator) (?&thirtyonedays) (?&separator) (?&year) (?!\d)
            |
            (?&shortmonthnumber) (?&separator) (?&thirtydays) (?&separator) (?&year) (?!\d)
            |
            (?&febmonth) (?&separator) (?&twentyeightdays) (?&separator) (?&year) (?!\d)
        )
        (?<dmy>
            (?&twentyninedays) (?&separator) (?&febmonth) (?&separator) (?&leapyear)  (?!\d)
            |
            (?&thirtyonedays) (?&separator) (?&longmonthnumber) (?&separator)(?&year) (?!\d)
            |
            (?&thirtydays) (?&separator) (?&shortmonthnumber) (?&separator) (?&year) (?!\d)
            |
            (?&twentyeightdays) (?&separator) (?&febmonth) (?&separator)  (?&year) (?!\d)
        )
        (?<date>
            (?&ymd) | (?&mdy) | (?&dmy)
        )
        (?<exact_date>
           ^(?&date)$
       )
    )
}x;

my @test = ( "2009-02-29", "2009-02-28", "2004-02-28", "2004-02-29", "2005-03-31", "2005-04-31", "2005-05-31", 
    "28-02-2009","02-28-2009",        
);

for (@test) {
  if ( $_ =~ m/(?&exact_date) $date_syntax/x ) {
    print "$_ is valid\n";
  }
  else {
    print "$_ is not valid\n";
  }

  if ( $_ =~ m/^(?&ymd) $date_syntax/x ) {
    print "$_ is valid ymd\n";
  }
  else {
    print "$_ is not valid ymd\n";
  }


  if ( $_ =~ m/^(?&leapyear) $date_syntax/x ) {
    print "$_ is leap (start)\n";
  }
  else {
    print "$_ is not leap (start)\n";
  }

  print "\n";
}

(?!\d)スニペットの追加に注意してください。スニペットは次のように追加されます。

「4」が0に一致するため、「45」~= m{(?&twentyeightdays) $syntax}は一致しませんか?[4]

于 2009-06-12T20:23:10.857 に答える
1

これは、条件が非常に多くある場合に、複数行を一貫してインデントする方法の問題のようです。本当に重要なのは一貫性です。perltidyまたはその他のフォーマッターを使用する場合は、(構成で)思いついたものと一致してください。ただし、正規表現の内容を区切り文字から1レベルインデントします。

あなたの投稿は、Perl::Criticのようなものを通して既存のコードを実行する際の1つの大きな欠陥を示しています-あなたはCPANの例が元の正規表現から*を省略しました。多くの「クリーンアップ」を行うと、バグが発生する可能性がありますので、良いテストスイートを用意してください。

于 2009-06-12T16:03:13.663 に答える