Регулярка готова, половина тестовых строк совпадает, остальные нет. Дорого всегда не написание, а поиск причины. Три ловушки ниже покрывают большинство случаев, и у каждой есть фиксированный порядок проверки.
Разделите два вида неудачи
Нет совпадения и совпадение с неверным содержимым — разные проблемы. Первое обычно означает неверный класс символов или флаг, второе почти всегда связано с жадными квантификаторами и расположением скобок. Определив тип, вы экономите половину времени: сначала проверьте, есть ли совпадение, затем — что попало в первую группу.
Ловушка 1: жадный квантификатор съедает остаток
.* по умолчанию жадный: он доходит до конца строки и затем откатывается. Поэтому title="(.*)" на строке title="a" id="b" вернёт a" id="b, а не a.
Два решения: сделать квантификатор ленивым (.*?) или использовать точный класс символов ([^"]*). Второй вариант обычно быстрее и стабильнее — ленивые квантификаторы всё равно откатываются, а классы символов нет.
Ловушка 2: лишний (или недостающий) уровень экранирования
Когда регулярка записана в строковом литерале, обратные слэши проходят через два парсера. В JavaScript нужно писать \\d, чтобы получить регулярку \d; если скопировать \\\\d откуда-то, шаблон начнёт искать буквальный обратный слэш и d. То же с \s, \. и \/.
Быстрая проверка: выведите итоговую строку шаблона в отладчике и сравните с ожидаемой. Это снимает большую часть случаев «я же написал, а оно не совпадает».
Ловушка 3: флаги m и g
Без m символы ^ и $ совпадают только с началом и концом всей строки, поэтому построчная проверка в многострочном тексте не работает. С m они учитывают границы каждой строки.
С g объект регулярки запоминает позицию последнего совпадения (lastIndex). Если использовать одну и ту же регулярку с g в цикле, второй вызов продолжит с прошлой позиции — выглядит как пропуск совпадений. Создавайте новый экземпляр на каждую итерацию или сбрасывайте lastIndex в ноль.
Порядок проверки из четырёх шагов
- Сократить вход: оставить минимальную строку, на которой ошибка воспроизводится
- Разделить шаблон: разбить по
|или убрать необязательные части, пока не найдётся сбойный участок - Посмотреть группы: проверить, что реально попало в каждую группу
- Проверить другие входы: добавить строку, которая должна совпасть, и строку, которая не должна
Четвёртый шаг пропускают чаще всего. Совпадение с целью не доказывает, что шаблон отвергает остальное — лишние совпадения вредят сильнее, например когда вместо простого текста заменяется целый фрагмент HTML.
Несколько слов о производительности
Вложенные квантификаторы вроде (a+)+$ на длинном несовпадающем вводе вызывают катастрофический откат и могут заморозить браузер на секунды. Проверка простая: удвойте длину входа и посмотрите, выросло ли время больше чем в четыре раза. Если да — не применяйте такой шаблон напрямую к пользовательскому вводу.
Одной фразой
Сначала определите, нет совпадения или неверное содержимое, затем проверяйте жадность, экранирование и флаги именно в этом порядке.
Три числа, которые читают первыми
Открывая тестер регулярок, отвлекитесь от красивой подсветки и посмотрите сначала на три вещи:
- Общее число совпадений. Ноль означает «не совпало вообще ничего» — проблема в наборе символов или флаге. Больше нуля, но меньше ожидаемого — обычно забыт флаг
gлибо какая-то часть шаблона съедает всё после себя. - Начальная и конечная позиция каждого совпадения. Позиции объясняют больше, чем содержимое: если первое совпадение тянется до конца текста, виноват жадный квантификатор.
- Что реально попало в группу 1. Взгляд на группу отдельно сразу разделяет «не совпало» и «совпало, но взято не то».
Порядок важен: сначала количество, потом позиции, затем группы. В обратном порядке цветная подсветка уведёт взгляд, и всё будет казаться неправильным.
Три слепые зоны в CJK и Unicode
w — это сокращение для [A-Za-z0-9_], и иероглифов там нет вообще, поэтому w+ по строке «张三» вернёт пусто. Для китайского текста используйте [一-龥] или более широкий [一-鿿].
. не совпадает с переводом строки без флага s. На многострочном тексте .* остановится в конце первой строки. Либо [sS]*, либо включите s.
Границы слова для китайского почти бесполезны: слова не разделены пробелами, границы просто нет. Сегментация требует отдельной библиотеки, а не регулярки.
И ещё: китайские знаки препинания полноширинные. , и , — разные кодовые точки, поэтому форма, проверяющая полуширинную запятую, отвергнет пользователя, набравшего полноширинную.
Закрепите шаблон таблицей случаев
«При проверке проходило» не значит, что регулярка верна. Надёжный подход — три-пять случаев на каждый шаблон, записанных в небольшую таблицу:
- Должно совпасть: обычный ввод плюс граничные — самый короткий, самый длинный, ровно на границе.
- Не должно совпасть: пустая строка, только пробелы, грязные данные с лишними префиксами или суффиксами.
- Склонно к лишним совпадениям: похожие, но иные по классу входы — для телефона номер с префиксом или городской, начинающийся с 1.
Таблица окупается позже: когда через полгода кто-то правит шаблон, прогон случаев сразу покажет, не сломалось ли прежнее поведение. Без таблицы любая правка — правка вслепую.
Где регулярке не место
Регулярное выражение — не универсальный инструмент, в некоторых местах оно обречено:
- Разбор вложенного HTML или XML. Теги вкладываются произвольно, а рекурсии у регулярок нет.
<div>.*?</div>всегда остановится на первом закрывающем теге и ошибётся во внешней структуре. Нужен DOM-парсер. - Разбор JSON. В строках допустимы любые экранирования и скобки, регулярка их перепутает. Нужен
JSON.parse. - Полная проверка адреса почты. RFC 5322 допускает огромное разнообразие форм, а погоня за 100% точностью тяжела в написании и поддержке. Практично: ограничить длину, требовать один
@и хотя бы одну точку, затем отправить письмо с подтверждением. - Очень длинный ввод. Шаблон с вложенными квантификаторами даёт экспоненциальный откат на десятках тысяч несовпадающих символов и вешает браузер. Сначала ограничьте длину.
Критерий прост: допускает ли целевая структура произвольную вложенность или рекурсию? Если да — регулярка не подходит.