Есть ли лучший способ писать регулярные выражения Perl с / x, чтобы код все еще легко читался? - PullRequest
8 голосов
/ 12 июня 2009

Я запустил 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 - это просто руководство, но было бы неплохо следовать ему.

Заранее спасибо!

EDIT: Поэтому после получения нескольких ответов мне стало ясно, что создание регулярного выражения с комментариями не всегда необходимо. Люди, которые разбираются в базовых регулярных выражениях, должны понимать, что делает мой пример - комментарии, которые я добавил, были, возможно, немного ненужными и многословными. Мне нравится идея использования расширенного флага регулярных выражений, но все же встраивание пробелов в регулярное выражение, чтобы сделать каждую часть регулярного выражения немного более понятной. Спасибо за весь вклад!

Ответы [ 5 ]

12 голосов
/ 12 июня 2009

Никогда не пишите комментарий, который говорит, что говорит код. Комментарии должны сказать вам, почему код говорит то, что говорит. Взгляните на это чудовище, без комментариев очень трудно увидеть, что происходит, но комментарии дают понять , что пытается найти соответствие:

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$/;
11 голосов
/ 12 июня 2009

Ну, я действительно не думаю, что вы должны тратить впустую недвижимость на вертикальном экране. С другой стороны, если бы я написал этот шаблон в несколько строк, я бы использовал фигурные скобки и отступ от шаблона:

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

ИМХО, отлично подходит следующая версия:

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

с точки зрения получения выгоды m//x.

В этом случае комментарии совершенно не нужны, потому что вы не делаете ничего хитрого. Я добавил \s* перед точкой с запятой, потому что иногда люди устанавливают точку с запятой отдельно от имени пакета, и это не должно сбрасывать ваш матч.

8 голосов
/ 12 июня 2009

Это в значительной степени ваш призыв к добавленной стоимости такой дополнительной информации.

Иногда вы правы, это не добавляет ничего, чтобы объяснить, что происходит, и просто делает код неопрятным, но для сложных регулярных выражений флаг x может быть благом.

На самом деле, это «вызов» относительно добавленной стоимости дополнительной информации может быть довольно трудным.

Я не могу вспомнить, сколько раз я видел унаследованный код, где красиво отформатированные комментарии не были сохранены и настолько отклонялись от того, что делает код. На самом деле, когда я был менее опытным, я пошел по неверному пути, потому что комментарий, связанный с фрагментом кода, был старым и не поддерживался.

Редактировать: В некоторых случаях пример CPAN не очень полезен. При использовании флага x для добавления комментариев для описания сложного регулярного выражения, я склонен описывать компоненты, которым регулярное выражение пытается соответствовать, а не просто описывать сами «биты» регулярного выражения. Например, я бы написал что-то вроде:

  • первый компонент (область и район) почтового индекса Великобритании или
  • международный код города для Великобритании или
  • любой номер мобильного телефона в Великобритании.

, который говорит мне больше, чем

  • одна или две буквы, за которыми следует число, за которым, возможно, следует буква, или
  • две четыре цифры вместе или
  • ноль, за которым следуют четыре десятичных знака, тире, а затем шесть десятичных знаков.

В этом случае я чувствую, что оставил бы комментарии регулярного выражения. Ваше внутреннее чувство верно!

6 голосов
/ 13 июня 2009

В этой теме рассказывается об альтернативных способах написания регулярных выражений, есть способы написания сложных регулярных выражений без переменных и без комментариев, и это по-прежнему полезно.

Я перефразировал регулярное выражение даты Chas Owens в новую декларативную форму, доступную в 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) фрагментов, которые добавляются так, чтобы

"45" не будет совпадать ~= m{(?&twentyeightdays) $syntax} из-за совпадения "4" 0? [4]

1 голос
/ 12 июня 2009

Похоже, это больше вопрос о том, как последовательно делать отступы многострочного, если условие ... на которое существует множество ответов. Что действительно важно, так это последовательность. Если вы используете perltidy или какой-либо другой форматер, будьте согласны с тем, что он предлагает (с вашей конфигурацией). Я бы сделал отступ в содержании регулярного выражения на один уровень от разделителей.

Ваш пост показывает один серьезный недостаток в запуске существующего кода через что-то вроде Perl :: Critic - you в примере CPAN опущен * из исходного регулярного выражения Если вы делаете много «очистки», вы можете ожидать появления ошибок, поэтому я надеюсь, что у вас есть хороший набор тестов.

...