Как мне написать больше поддерживаемых регулярных выражений?
Я начал чувствовать, что использование регулярных выражений снижает удобство сопровождения кода. В краткости и силе регулярных выражений есть что-то плохое. Perl сочетает это с побочными эффектами, такими как операторы по умолчанию.
У меня есть привычка документировать регулярные выражения, по крайней мере, с одним предложением, дающим основное намерение, и, по крайней мере, один пример того, что будет соответствовать.
Поскольку регулярные выражения созданы, я чувствую, что абсолютно необходимо комментировать самые большие компоненты каждого элемента в выражении. Несмотря на это, даже мои регулярные выражения заставляют меня чесать голову, как будто я читаю клингон.
Вы намеренно тупите свои регулярные выражения? Разлагаете ли вы, возможно, более короткие и более мощные на более простые шаги? Я отказался от вложенных регулярных выражений. Существуют ли конструкции регулярных выражений, которых вы избегаете из-за проблем с mainainability?
Не позволяйте этому примеру затуманить вопрос.
Если у следующего Майкла Эша есть какая-то ошибка, есть ли у вас какие-либо шансы сделать что-нибудь, кроме как полностью выбросить это?
^(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(?:(?:0?[13-9]|1[0-2])(\/|-|\.)(?:29|30)\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.)(?:0?[1-9]|1\d|2[0-8])\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$
По запросу точная цель может быть найдена, используя ссылку мистера Эша выше.
Матчи 01.1.02 | 11-30-2001 | 2/29/2000
Несоответствия 29.02.01 | 13/01/2002 | 11/00/02
13 ответов
Я обычно просто пытаюсь обернуть все свои вызовы Регулярного выражения в их собственную функцию со значимым именем и некоторыми основными комментариями. Мне нравится думать о Регулярных выражениях как о языке только для записи, доступном для чтения только тому, кто его написал (если только он не очень прост). Я полностью ожидаю, что кому-то, возможно, придется полностью переписать выражение, если ему придется изменить его намерение, и, вероятно, это будет лучше для поддержания обучения Регулярным выражениям.
Use Expresso which gives a hierarchical, english breakdown of a regex.
Или же
This tip from Darren Neimke:
.NET allows regular expression patterns to be authored with embedded comments via the RegExOptions.IgnorePatternWhitespace compiler option and the (?#...) syntax embedded within each line of the pattern string.
Это позволяет встраивать комментарии, подобные псевдо-коду, в каждую строку и оказывает следующее влияние на удобочитаемость:
Dim re As New Regex ( _
"(?<= (?# Start a positive lookBEHIND assertion ) " & _
"(#|@) (?# Find a # or a @ symbol ) " & _
") (?# End the lookBEHIND assertion ) " & _
"(?= (?# Start a positive lookAHEAD assertion ) " & _
" \w+ (?# Find at least one word character ) " & _
") (?# End the lookAHEAD assertion ) " & _
"\w+\b (?# Match multiple word characters leading up to a word boundary)", _
RegexOptions.Multiline Or RegexOptions.IgnoreCase Or RegexOptions.IgnoreWhitespace _
)
Вот еще один пример.NET (требуется RegexOptions.Multiline
а также RegexOptions.IgnorePatternWhitespace
опции):
static string validEmail = @"\b # Find a word boundary
(?<Username> # Begin group: Username
[a-zA-Z0-9._%+-]+ # Characters allowed in username, 1 or more
) # End group: Username
@ # The e-mail '@' character
(?<Domainname> # Begin group: Domain name
[a-zA-Z0-9.-]+ # Domain name(s), we include a dot so that
# mail.somewhere is also possible
.[a-zA-Z]{2,4} # The top level domain can only be 4 characters
# So .info works, .telephone doesn't.
) # End group: Domain name
\b # Ending on a word boundary
";
Если ваш RegEx применим к общей проблеме, другой вариант - документировать его и отправить в RegExLib, где он будет оценен и прокомментирован. Ничто не сравнится со многими парами глаз...
Еще одним инструментом RegEx является регулятор
Итак, вся цель модификатора PCRE /x в жизни состоит в том, чтобы вы могли писать регулярные выражения более наглядно, как в этом тривиальном примере:
my $expr = qr/
[a-z] # match a lower-case letter
\d{3,5} # followed by 3-5 digits
/x;
Некоторые люди используют RE для неправильных вещей (я жду первого SO вопроса о том, как обнаружить действительную программу C++, используя один RE).
Я обычно нахожу, что, если я не могу уместить свой RE в пределах 60 символов, лучше быть частью кода, так как это почти всегда будет более читабельным.
В любом случае, я всегда документирую в коде, что должен достичь RE, очень подробно. Это потому, что я знаю по горькому опыту, как трудно кому-то другому (или даже мне, через полгода) прийти и попытаться понять.
Я не верю, что они злые, хотя я верю, что некоторые люди, которые их используют, злы (не глядя на вас, Майкл Эш:-). Это отличный инструмент, но, как бензопила, вы отрезаете ноги, если не знаете, как правильно их использовать.
ОБНОВЛЕНИЕ: На самом деле, я только что перешел по ссылке на это чудовище, и это для проверки дат формата m/d/y между 1600 и 9999 годами. Это классический случай, когда полноценный код будет более читабельным и обслуживаемым,
Вы просто разбиваете его на три поля и проверяете отдельные значения. Я бы почти посчитал это преступлением, достойным прекращения, если бы один из моих миньонов купил это мне. Я, конечно, отправил бы их обратно, чтобы написать это правильно.
Вот то же самое регулярное выражение, разбитое на усваиваемые кусочки. В дополнение к тому, чтобы быть более удобочитаемыми, некоторые из под-регулярных выражений могут быть полезны сами по себе. Также значительно проще изменить допустимые разделители.
#!/usr/local/ActivePerl-5.10/bin/perl
use 5.010; #only 5.10 and above
use strict;
use warnings;
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$/;
say "test against garbage";
for my $date (qw(022900 foo 1/1/1)) {
say "\t$date ", $date ~~ $only_date ? "matched" : "didn't match";
}
say '';
#comprehensive test
my @code = qw/good unmatch month day year leap/;
for my $sep (qw( / - . )) {
say "testing $sep";
my $i = 0;
for my $y ("00" .. "99", 1600 .. 9999) {
say "\t", int $i/8500*100, "% done" if $i++ and not $i % 850;
for my $m ("00" .. "09", 0 .. 13) {
for my $d ("00" .. "09", 1 .. 31) {
my $date = join $sep, $m, $d, $y;
my $re = $date ~~ $only_date || 0;
my $code = not_valid($date);
unless ($re == !$code) {
die "error $date re $re code $code[$code]\n"
}
}
}
}
}
sub not_valid {
state $end = [undef, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
my $date = shift;
my ($m,$d,$y) = $date =~ m{([0-9]+)[-./]([0-9]+)[-./]([0-9]+)};
return 1 unless defined $m; #if $m is set, the rest will be too
#components are in roughly the right ranges
return 2 unless $m >= 1 and $m <= 12;
return 3 unless $d >= 1 and $d <= $end->[$m];
return 4 unless ($y >= 0 and $y <= 99) or ($y >= 1600 and $y <= 9999);
#handle the non leap year case
return 5 if $m == 2 and $d == 29 and not leap_year($y);
return 0;
}
sub leap_year {
my $y = shift;
$y = "19$y" if $y < 1600;
return 1 if 0 == $y % 4 and 0 != $y % 100 or 0 == $y % 400;
return 0;
}
Я научился избегать всего, кроме простейшего регулярного выражения. Я предпочитаю другие модели, такие как сканирование строк в Icon или парсинг-комбинаторы Haskell. В обеих этих моделях вы можете написать пользовательский код, который имеет те же привилегии и статус, что и встроенные строковые операции. Если бы я программировал на Perl, я бы, вероятно, установил несколько синтаксических анализаторов на Perl - я сделал это для других языков.
Очень хорошая альтернатива - использовать грамматики синтаксического анализа, как это сделал Роберто Иерусалимский со своим пакетом LPEG, но в отличие от комбинаторов синтаксического анализатора, это то, что вы не можете разыграть днем. Но если кто-то уже сделал PEG для вашей платформы, это очень хорошая альтернатива регулярным выражениям.
Вау, это безобразно Похоже, что это должно работать, по модулю неизбежная ошибка, имеющая дело с 00 как двухзначный год (это должен быть високосный год четверть времени, но без столетия у вас нет возможности узнать, каким он должен быть). Существует много избыточности, которая, вероятно, должна быть разбита на подвыражения, и я бы создал три подрегекса для трех основных случаев (это мой следующий проект сегодня вечером). Я также использовал другой символ для разделителя, чтобы избежать необходимости экранировать косую черту, изменил чередование отдельных символов на классы символов (что, к счастью, позволяет нам избежать необходимости избегать точки), и изменил \d
в [0-9]
поскольку первый соответствует любому символу цифры (включая U+1815
MONGOLIAN DIGIT FIVE
: ᠕) в Perl 5.8 и 5.10.
Предупреждение, непроверенный код:
#!/usr/bin/perl
use strict;
use warnings;
my $match_date = qr{
#match 29th - 31st of all months but 2 for the years 1600 - 9999
#with optionally leaving off the first two digits of the year
^
(?:
#match the 31st of 1, 3, 5, 7, 8, 10, and 12
(?: (?: 0? [13578] | 1[02] ) ([/-.]) 31) \1
|
#or match the 29th and 30th of all months but 2
(?: (?: 0? [13-9] | 1[0-2] ) ([/-.]) (?:29|30) \2)
)
(?:
(?: #optionally match the century
1[6-9] | #16 - 19
[2-9][0-9] #20 - 99
)?
[0-9]{2} #match the decade
)
$
|
#or match 29 for 2 for leap years
^
(?:
#FIXME: 00 is treated as a non leap year
#even though 2000, 2400, etc are leap years
0?2 #month 2
([/-.]) #separtor
29 #29th
\3 #separator from before
(?: #leap years
(?:
#match rule 1 (div 4) minus rule 2 (div 100)
(?: #match any century
1[6-9] |
[2-9][0-9]
)?
(?: #match decades divisible by 4 but not 100
0[48] |
[2468][048] |
[13579][26]
)
|
#or match rule 3 (div 400)
(?:
(?: #match centuries that are divisible by 4
16 |
[2468][048] |
[3579][26]
)
00
)
)
)
)
$
|
#or match 1st through 28th for all months between 1600 and 9999
^
(?: (?: 0?[1-9]) | (?:1[0-2] ) ) #all months
([/-.]) #separator
(?:
0?[1-9] | #1st - 9th or
1[0-9] | #10th - 19th or
2[0-8] #20th - 28th
)
\4 #seprator from before
(?:
(?: #optionally match the century
1[6-9] | #16 - 19
[2-9][0-9] #20 - 99
)?
[0-9]{2} #match the decade
)
$
}x;
Я нашел хороший способ - просто разбить процесс сопоставления на несколько этапов. Вероятно, он не выполняется так быстро, но у вас есть дополнительный бонус в том, что вы также можете сказать на более высоком уровне зерна, почему совпадение не происходит.
Другой способ - использовать LL или LR-разбор. Некоторые языки не могут быть выражены как регулярные выражения, вероятно, даже с расширениями perl, не относящимися к fsm.
Некоторые люди, сталкиваясь с проблемой, думают: "Я знаю, я буду использовать регулярные выражения". Теперь у них две проблемы. - Джейми Завински в comp.lang.emacs.
Сохраняйте регулярные выражения настолько простыми, насколько это возможно ( ПОЦЕЛУЙ). В вашем примере с датой я бы, вероятно, использовал одно регулярное выражение для каждого типа даты.
Или, что еще лучше, замените его библиотекой (то есть библиотекой разбора даты).
Я также предпринял бы шаги, чтобы гарантировать, что у входного источника есть некоторые ограничения (то есть только один тип строк даты, в идеале ISO-8601).
Также,
- Одна вещь в то время (с возможным исключением извлечения значений)
- Расширенные конструкции хороши, если используются правильно (например, в упрощении выражения и, следовательно, в сокращении обслуживания).
РЕДАКТИРОВАТЬ:
"передовые конструкции приводят к проблемам обслуживания"
Моя первоначальная идея заключалась в том, что при правильном использовании это должно приводить к более простым выражениям, а не к более сложным. Более простые выражения должны уменьшить обслуживание.
Я обновил текст выше, чтобы сказать как можно больше.
Я хотел бы отметить, что регулярные выражения вряд ли могут рассматриваться как сложные конструкции сами по себе. Незнание определенной конструкции не делает ее продвинутой, просто незнакомой. Что не меняет того факта, что регулярные выражения являются мощными, компактными и - при правильном использовании - элегантными. Как и скальпель, он целиком находится в руках того, кто им владеет.
Я думаю, что ответ на поддержание регулярного выражения заключается не столько в комментариях или регулярных выражениях.
Если бы мне было поручено отладить приведенный вами пример, я бы сел перед инструментом отладки регулярных выражений (например, Regex Coach) и перебрал регулярное выражение для данных, которые он должен обрабатывать.
Я все еще мог бы работать с этим. Я бы просто использовал Регулятор. Одна вещь, которую он позволяет вам сделать, это сохранить регулярное выражение вместе с тестовыми данными для него.
Конечно, я мог бы также добавить комментарии.
Вот что сделал Expresso. Я никогда не использовал его раньше, но теперь у меня нет работы:
// используя System.Text.RegularExpressions; /// /// Регулярное выражение для C# on: чт, 2 апреля 2009 г., 12:51:56 утра /// Использование Expresso версии: 3.0.3276, http://www.ultrapico.com /// /// Описание регулярного выражения: /// /// Выбор из 3 альтернатив /// ^(?:(?:(?:0?[13578]|1[02])(\/|-|\.)31)\1|(:(?: 0 [13-9] | 1 [0-2]) (\ / | - |?. \) (?: 29 | 30) \ 2)) (?:(?: 1 [6-9] | [2-9] \ d)? \ D {2})$ /// Начало строки или строки /// Соответствует выражению, но не захватывает его. [(:(?: 0 [13578] | 1 [02]?)? (\ / | - |. \) 31) \ 1 | (:(?: 0 [13-9] | 1 [0- 2]) (\ / | - | \.) (?: 29 | 30) \ 2)] /// Выберите один из 2 вариантов /// (?:(?:0?[13578]|1[02])(\/|-|\.)31)\1 /// Соответствует выражению, но не захватывает его. [(?:0?[13578]|1[02])(\/|-|\.)31] /// (?:0?[13578]|1[02])(\/|-|\.)31 /// Подходим выражение, но не фиксируем его. [0?[13578]|1[02]] /// Выбор из 2 альтернатив /// 0?[13578] /// 0, ноль или одно повторение /// Любой символ в этом классе: [13578] /// 1[02] /// 1 /// Любой символ в этом классе: [02] /// [1]: пронумерованная группа захвата. [\ / | - | \.] /// Выберите один из 3 вариантов /// Литерал / /// - /// Литерал. /// 31 /// Ссылка на номер захвата: 1 /// (?:(?: 0? [13-9] | 1 [0-2]) (\ / | - | \.) (?: 29 | 30) \ 2) /// Возврат /// Новая строка /// Соответствует выражению, но не захватывает его. [(?: 0? [13-9] | 1 [0-2]) (\ / | - | \.) (?: 29 | 30) \ 2] /// (?: 0? [13-9 ] | 1 [0-2]) (\ / | - | \.) (?: 29 | 30) \ 2 /// Соответствует выражению, но не захватывает его. [0? [13-9] | 1 [0-2]] /// Выбор из 2 альтернатив /// 0? [13-9] /// 0, ноль или одно повторение /// Любой символ в этом классе: [13-9] /// 1 [0-2] /// 1 /// Любой символ в этом классе: [0-2] /// [2]: пронумерованная группа захвата. [\ / | - | \.] /// Выберите один из 3 вариантов /// Литерал / /// - /// Литерал. /// Подходим выражение, но не фиксируем его. [29 | 30] /// Выберите из 2 альтернатив /// 29 /// 29 /// 30 /// 30 /// Обратная ссылка для ввода номера: 2 /// Возврат /// Новая строка /// Соответствие выражение, но не захватывать его. [(?: 1 [6-9] | [2-9] \ d)? \ D {2}] /// (?: 1 [6-9] | [2-9] \ d)? \ D {2} /// Подходим выражение, но не фиксируем его. [1 [6-9] | [2-9] \ d], ноль или одно повторение /// Выберите из 2 вариантов /// 1 [6-9] /// 1 /// Любой символ в этом классе: [6-9] /// [2-9] \ d /// Любой символ в этом классе: [2-9] /// Любая цифра /// Любая цифра, ровно 2 повторения /// Конец строки или строка /// ^(?:0?2(\/|-|\.)29\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$ /// Начало строки или строки /// Соответствует выражению, но не фиксирует его. [0 2? | - | (?:(:(?: 1 [6-9] | [2-9] \ г)? (\ /. \) 29 \ 3 (0?: [48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))] /// 0?2(\/|-|\) 29 \ 3 (:(:(?: 1 [6-9] | [2-9] \ г) (0?: [48] | [2468] [048] | [.??? 13579][26])|(?:(?:16|[2468][048]|[3579][26])00))) /// 0, ноль или одно повторение2 /// [3]: A пронумерованная группа захвата. [\ / | - | \.] /// Выберите один из 3 вариантов /// Литерал / /// - /// Литерал. /// 29 /// Ссылка на номер захвата: 3 /// Соответствует выражению, но не захватывает его. [(:(?: 1 [6-9] |? [2-9] \ г)? (0?: [48] | [2468] [048] | [13579] [26]) |? (:(?:16|[2468][048]|[3579][26])00))] /// Сопоставить выражение, но не перехватить его. [(?: 1 [6-9] | [2-9] \ г)? (0?: [48] | [2468] [048] | [13579] [26]) | (:(?:16|[2468][048]|[3579][26])00)] /// Выбрать из 2 вариантов /// (?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26]) /// Соответствует выражению, но не захватывает его. [1 [6-9] | [2-9] \ d], ноль или одно повторение /// Выберите из 2 вариантов /// 1 [6-9] /// 1 /// Любой символ в этом классе: [6-9] /// [2-9] \ d /// Любой символ в этом классе: [2-9] /// Любая цифра /// Соответствует выражению, но не захватывает его. [0[48]|[2468][048]|[13579][26]] /// Выберите из 3 вариантов /// 0 [48] /// 0 /// Любой символ в этом классе: [48] /// [2468] [048] /// Любой символ в этом классе: [2468] /// Любой символ в этом классе: [048] /// [13579][26] /// Любой символ в этом классе: [13579] /// Любой символ в этом классе: [26] /// (?:(?:16|[2468][048]|[3579][26])00) /// Возврат /// Новая строка /// Соответствует выражению, но не захватывает его. [(?:16|[2468][048]|[3579][26])00] /// (?:16|[2468][048]|[3579][26])00 /// Соответствие выражению но не поймай это. [16|[2468][048]|[3579][26]] /// Выберите из 3 вариантов /// 16 /// 16 /// [2468] [048] /// Любой символ в этом классе: [2468] /// Любой символ в этом классе: [048] /// [3579][26] /// Любой символ в этом классе: [3579] /// Любой символ в этом классе: [26] // / 00 /// Конец строки или строки /// ^(?:(?:0?[1-9])|(?:1[0-2]))(\/|-|\.)(?:??? 0 [1-9] | 1 \ д | 2 [0-8]) \ 4 (:(?: 1 [6-9] | [2-9] \ d) \d{2})$ /// Начало строки или строки /// Соответствует выражению, но не перехватывает его. [(?: 0? [1-9]) | (?: 1 [0-2])] /// Выберите из 2 альтернатив /// Подберите выражение, но не захватывайте его. [0? [1-9]] /// 0? [1-9] /// 0, ноль или одно повторение /// Любой символ в этом классе: [1-9] /// Соответствует выражению, но не захватить это. [1 [0-2]] /// 1 [0-2] /// 1 /// Любой символ в этом классе: [0-2] /// Возврат /// Новая строка /// [4]: Пронумерованная группа захвата. [\ / | - | \.] /// Выберите один из 3 вариантов /// Литерал / /// - /// Литерал. /// Подходим выражение, но не фиксируем его. [0? [1-9] | 1 \ d | 2 [0-8]] /// Выберите из 3 вариантов /// 0? [1-9] /// 0, ноль или одно повторение /// Любое символ в этом классе: [1-9] /// 1 \ d /// 1 /// Любая цифра /// 2 [0-8] /// 2 /// Любой символ в этом классе: [0- 8] /// Ссылка на номер захвата: 4 /// Соответствует выражению, но не захватывает его. [(?: 1 [6-9] | [2-9] \ d)? \ D {2}] /// (?: 1 [6-9] | [2-9] \ d)? \ D {2} /// Подходим выражение, но не фиксируем его. [1 [6-9] | [2-9] \ d], ноль или одно повторение /// Выберите из 2 вариантов /// 1 [6-9] /// 1 /// Любой символ в этом классе: [6-9] /// [2-9] \ d /// Любой символ в этом классе: [2-9] /// Любая цифра /// Любая цифра, ровно 2 повторения /// Конец строки или строка /// /// /// public static Regex regex = new Regex( "^(?:(?:(?:0?[13578]|1[02])(\\/|-|\\.)31)\\1|\r\n(?:(?:0?[13-9]"+ "|1[0-2])(\\/|-|\\.)(?:29|30)\\2))\r\n(?:(?:1[6-9]|[2-9]\\d)?\\d"+ "{2})$|^(?: | - | (?:(:(?: 1 [6-9] | [2-9] \\ д)? 0 2 (. \\ / \\) 29 \\ 3 (?:0["+ "48]|[2468][048]|[13579][26])|\r\n(?:(?:16|[2468][048]|[3579][2"+ "6])00))))$|^(:(?: 0 [1-9]) | (?:?. 1 [0-2])) \ г \ п (\\ / | - | \\)(?:0?[1-9"+ "]|1\\d|2[0-8])\\4(?:(?:1[6-9]|[2-9]\\d)?\\d{2})$", RegexOptions.CultureInvariant | RegexOptions.Compiled);
Недавно я опубликовал вопрос о комментировании регулярных выражений со встроенными комментариями. Были полезные ответы, в частности один из @mikej
Посмотрите пост Мартина Фаулера на ComposedRegex, чтобы узнать больше о том, как улучшить читаемость регулярных выражений. Таким образом, он выступает за разбиение сложного регулярного выражения на более мелкие части, которым можно дать значимые имена переменных. например
Я не ожидаю, что регулярные выражения будут читабельными, поэтому я просто оставляю их как есть и переписываю при необходимости.