Рекомендации в этой статье в основном сосредоточены на читабельности регулярных выражений. Развивая эти навыки во время разработки, вы будете более четко учитывать дизайн и структуру выражений, что поможет уменьшить количество ошибок и обслуживание кода. вы являетесь сопровождающим этого кода. Вы можете сами посмотреть и обратить внимание на этот опыт использования регулярных выражений в вашем реальном использовании.
Регулярные выражения сложно писать, читать и поддерживать. Они часто не совпадают с неожиданным текстом или пропускают допустимый текст. Эти проблемы вызваны производительностью и возможностями регулярных выражений. Сочетание возможностей и нюансов каждого метасимвола делает невозможным интерпретацию кода, не прибегая к интеллектуальным уловкам.
Многие инструменты включают в себя функции, которые упрощают чтение и запись регулярных выражений, но при этом они очень неидиоматичны. Для многих программистов написание регулярных выражений — волшебное искусство. Они придерживаются тех характеристик, которые знают, и настроены абсолютно оптимистично. Если вы готовы перенять пять привычек, обсуждаемых в этой статье, вы сможете создавать регулярные выражения, которые выдерживают метод проб и ошибок.
В этой статье в качестве примеров кода будут использоваться языки Perl, PHP и Python, но советы в этой статье применимы практически к любой реализации выражения замены (регулярного выражения).
1. Используйте пробелы и комментарии.
Для большинства программистов использование пробелов и отступов в среде регулярных выражений не является проблемой. Если они этого не сделают, над ними обязательно будут смеяться коллеги и даже обычные люди. Почти все знают, что сжатие кода в одну строку затрудняет его чтение, запись и поддержку. В чем разница для регулярных выражений?
Большинство инструментов замены выражений имеют расширенную функцию пробелов, которая позволяет программистам расширять свои регулярные выражения на несколько строк и добавлять комментарии в конце каждой строки. Почему этой функцией пользуется лишь небольшое количество программистов? Регулярные выражения Perl 6 по умолчанию используют шаблоны, расширенные пробелами. Не позволяйте языку расширять пробелы по умолчанию, воспользуйтесь этим сами.
Один трюк, который следует помнить о расширенных пробелах, — это указать обработчику регулярных выражений игнорировать расширенные пробелы. Таким образом, если вам нужно сопоставить пробелы, вам придется указать это явно.
В языке Perl добавьте x в конце регулярного выражения, чтобы «m/foo bar/» принял следующий вид:
m/
фу
бар
/x
В языке PHP добавьте x в конце регулярного выражения, чтобы ""/foo bar/"" принял следующий вид:
"/
фу
бар
/x»
На языке Python передайте параметр модификации шаблона «re.VERBOSE», чтобы получить скомпилированную функцию следующим образом:
шаблон = 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} # Трехзначный префикс
[-.] # Еще один разделитель
d{4} # Четырехзначный номер телефона
/x
Переписанное регулярное выражение теперь имеет необязательный разделитель после кода города, поэтому оно должно соответствовать «314-555-4000», однако код города по-прежнему требуется. Другой программист, которому нужно сделать код города необязательным, быстро поймет, что теперь он не является необязательным, и небольшое изменение может решить проблему.
2.
При написании тестов существует три уровня тестирования. Каждый уровень добавляет уровень надежности вашему коду. Во-первых, вам нужно тщательно подумать о том, какие коды вам нужно сопоставить и можно ли справиться с несовпадениями. Во-вторых, вам нужно использовать экземпляры данных для проверки регулярного выражения. Наконец, вам необходимо формально пройти тестовую комиссию.
Решение о том, что сопоставлять, на самом деле означает поиск баланса между сопоставлением неправильных результатов и пропуском правильных результатов. Если ваше регулярное выражение слишком строгое, оно пропустит некоторые правильные совпадения, если оно слишком свободное, оно выдаст неправильное совпадение; Как только регулярное выражение будет реализовано в реальном коде, вы можете не заметить и того, и другого. Рассмотрим приведенный выше пример номера телефона, который соответствует «800-555-4000 = -5355». Неправильные совпадения на самом деле трудно обнаружить, поэтому важно заранее их спланировать и тщательно протестировать.
Продолжая пример с номером телефона: если вы подтверждаете номер телефона в веб-форме, вас может устроить десятизначный номер в любом формате. Однако если вы хотите отделить номера телефонов от большого объема текста, вам может потребоваться тщательно исключить ложные совпадения, не соответствующие требованиям.
Обдумывая данные, которые вы хотите сопоставить, запишите несколько сценариев. Напишите код, чтобы проверить регулярное выражение на конкретном сценарии. Для любого сложного регулярного выражения лучше всего написать небольшую программу для его проверки, которая может принимать следующую конкретную форму.
На языке 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 my $test (@tests) {
если ( $test =~ m/
(? # необязательные круглые скобки
d{3} # Требуемый код телефонной зоны
)? # необязательные круглые скобки
[-s.]? # Разделителем может быть тире, пробел или точка.
d{3} # Трехзначный префикс
[-s.] # Еще один разделитель
d{4} # Четырехзначный номер телефона
/х ) {
напечатайте «Соответствует $testn»;
}
еще {
print «Не удалось найти совпадение в $testn»;
}
}
На языке PHP:
<?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} # Трехзначный префикс
[-s.] # Еще один разделитель
d{4} # Четырехзначный номер телефона
/x";
foreach ($tests как $test) {
если (preg_match($regex, $test)) {
echo "Соответствует $test
;";
}
еще {
echo "Не удалось найти совпадение в $test
;";
}
}
?>;
На языке Python:
import
retests = ["314-555-4000",
«800-555-4400»,
«(314)555-4000»,
«314.555.4000»,
«555-4000»,
"aasdklfjklas",
"1234-123-12345"
]
шаблон = r'''
(? # необязательные круглые скобки
d{3} # Требуемый код телефонной зоны
)? # необязательные круглые скобки
[-s.]? # Разделителем может быть тире, пробел или точка.
d{3} # Трехзначный префикс
[-s.] # Еще один разделитель
d{4} # Четырехзначный номер телефона
'''
regex = re.compile(pattern, re.VERBOSE) для теста в тестах:
если regex.match(тест):
напечатайте «Соответствует», проверьте, «n»
еще:
print «Не удалось найти совпадение», 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», это позволит вам включать символы новой строки. Например, regex = "( file://w+)(//d +)" можно записать в следующем виде:
regex = r'''
(w+)
(д+)
'''