Search
Write a publication
Pull to refresh

Comments 51

Знаете, все, конечно, ИМХО. Но я занимаюсь регулярками практически с самого их появления, и то, что Вы описали в статье — скорее вред, чем польза.

Обьясню почему — вы привнесли в регулярки еще один язык высокого уровня, который во-первых замедляет их выполнение, во-вторых лишает кроссмплатформенности и быстроты проверки. Да, я понимаю, что можно в конце концов вытащить итоговое выражение и оно будет работать везде — но его еще собрать надо.

Кроме того, большинство программистов привыкли именно к такому виду, который Вы показали в начале статьи + есть огромное количество инструментов для визуализации и работы с регулярками.

Если хочется более-менее порядка и стройности в данных выражениях, то для этого есть флаг PCRE-EXTENDED и после этого у вас появляется возможность использовать пробелы, табуляцию и перевод строки для разделения кусков регулярного выражения, возможность писать комментарии, а если совместить это с именованием групп — то вообще все будет с читаемостью отлично. И при этом никаких лишних сущностей не требуется.

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

Насчет того, что привыкли – а насколько это ценно? В плюсах раньше триграфами писали - а глядишь, переучились. Можно простую метрику ввести. Берем сто человек, даем им почитать длинную регулярку и декомпозированную. Засекаем время, которое им потребуется для того, чтобы решить типичные задачи программиста: понять/рассказать, что в коде происходит, устранить ошибку, переместить код куда-то, модифицировать поведение и прочее. У меня определенный оптимизм есть насчет результатов.

То, к чему привыкли — очень ценно. Привычки, знаете-ли тяжело менять — что в интерфейсах, что в программировании. И если и менять — то должен быть очень веский повод для этого.

В приведенном вами варианте регулярка становится размазанной по коду, начинает зависеть от еще одного языка иперестает быть воспринимаемой как регулярка и читаемости прибавляется только на последнем шаге при сборке — оно того ИМХО не стоит никаким образом, тем более — не стоит того чтобы менять устоявшиеся практики, ибо минусов куда больше, чем плюсов.

В современном мире новых программистов приходит больше чем есть привыкнувших. А порог входа таки высокий.

В современном мире программисты стараются из кубиков складывать, а в нутро не лезть) Тем более — новые программисты. Есть такие вещи, которые не в тренде — регулярки именно оттуда.

На одной из конференций в 2021 делали опрос про регулярки — примерно 60% их знало, около 35% могли писать что-то, и только около 8% понимали как оно работает. А теперь внезапно возраст — первая группа 18-45, вторая — 20-45, третья 29-45. Так что увы и ах.

Это, естественно, не означает какую-то оценку ума — это лишь означает важность опыта. Я встречал и 18-летнего дева, который в регулярках был, как в своем родном болоте — просто потому, что ему было интересно, и он на системника учился — но это скорее исключение, чем правило.

А вот попытки перетащить то, что надо просто понимать в то, в чем ты уже понимаешь — это как раз черта новых программистов — не в обиду вам будет сказано. Это как вечные попытки натянуть классическое ООП на JS с прототипами, хотя последнее при понимании отнюдь не хуже. Ну и из-за количества этих программистов в конце-концов в JS таки втянули это дело, до регулярок пока не добрались т-т-т )

Думаю, что знало там тоже на уровне: знало, что звездочка - это повтор.

Программисты уже 70 лет строят высокоуровневые языки поверх низкоуровневых. Олдфагов с хекскодами я не видел уже лет 20 (с ассемблером не сильно меньше), но ворчание их хорошо помню. Но все равно люди будут это делать, потому что удобно всем.

Если высокоуровневый язык дает хорошие преимущества — почему бы и нет? Олдфаги здесь не причем, это простая рациональность.

А вот если преимущество сводится лишь к тому «хочу, чтобы было так, как я знаю сейчас и как мне удобно, а как там было до меня — все равно» — это, ИМХО, тупик.

тема неоднозначная. Нехватка читаемости регулярок очевидна и обычно при повышение читаемости кода всегда страдает производительность так, что это просто цена. А вот платить ее или нет - зависит сугубо от проекта. Если предметная пестрит обилием регулярок, то почему и не попробовать из декомпозировать. а если у вас дюжина регулярок на вес проект, то наверно это не оправданно.

UFO landed and left these words here

кроссмплатформенности 

извините, не удержался

замедляет их выполнение

А вот это сильно не правда. В компилируемых языках можно такие конструкции сделать статически вычисляемыми и оно ничем не будет отличаться от того же месива, что и сплошной строке. В каком-нибудь JS почти наверняка в какой-то момент сработает оптимизация hotpath, однако я слабо представляю ситуацию, когда вы с каждым циклом пересобираете регулярку заново. То есть единоразовый вызов сборки регулярки не сказать чтобы сильно замедлит ваш код. Если конечно вы не запускаете эти регулярки на чем-то особенном.

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

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

В статье примеры на JS, но суть статьи про декомпозицию регулярок, а не про JS.

Это было про оптимизацию, которую Вы затронули.

Ну и суть моего комментария тоже не про JS, а про то, что придумывать себе ветряные мельницы, а потом героически с ними бороться — плохо, ибо можно было всего этого избежать и вообще не заморачиваться со всем этим.

Декомпозиция регулярок возможна как стандартными, встроенными методами (о которых я писал в первом комментарии), так и спец. инструментарием, а не дополнительными сущностями на абсолютно другом языке.

Тогда можно сразу к старой доброй фразе вернуться - если у вас есть проблема и вы решаете её регулярками - у вас две проблемы.

Кстати, про кроссплатформенность пошутил, но забыл спросить, а какие собственно с этим могут быть проблемы? perl/js/c++/вставить свой вариант вроде везде относительно единообразно работают.

Регулярки — это хороший инструмент для решения определенного круга задач — если это держать в голове — проблем не будет. В реальной жизни также — попытка отверткой забивать гвозди обычно оканчивается печально.

С кроссплатформенностью тоже есть нюансы — как и любой инструмент — регулярки развивались, мало того, что там существуют как минимум два стандарта, так еще и они внутри могут отличаться — например не поддерживать именованные группы — о чем тут уже было. Не поддерживать просмотр вперед/назад (или даже отмену или включение жадности). Есть очень интересная и крутая фича современных регулярок — рекурсивные регулярки (правда она еще сложнее для понимания, чем обычные варианты) — вот она поддерживается далеко не всеми. Короче, нюансов тоже предостаточно.

А, ну то есть кроссдвижковость, а не кроссплатформенность.

Ну можно и так сказать. Просто платформа для меня — это еще и система, а не только железо. Например второй стандарт до сих пор живет активно именно под *nix-системами, под win его и не встретить щас. Ну а железо может накладывать ограничение на глубину просмотра, а также на кол-во захватываемых групп и тд.

Но вообще, если брать PCRE — то таки да, с небольшими допущениями он практически везде работает одинаково.

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

Для того, чтобы рег выражение потом проверить где-то на стороне - его надо собрать из исходного кода, что капец гемор будет в вашем случае.

function wholeInput(regex) {
   return  "/^" + regex + "$/";
}
function zeroOrMore(regex) {
   return "(" + regex + ")*";
}
const or = "|"
const onlyOneWhiteSpace="\\s(?!\\s)";
const suffix = zeroOrMore ("[a-zA-Z0-9\\!\\#\\%]" + or + onlyOneWhiteSpace)
const someEngPattern = wholeInput( "[A-Z0-9]+" + suffix)


Я вот не могу согласиться с тем, что этот код менее мерзкий.
Я знаю синтаксис регулярок, мне не нужно объяснять, что (regex)* — это сколько угодно повторов, я это сразу вижу. Если уж что-то и нужно объяснять, то мотивацию того, почему или зачем вы делаете именно так.
Зато регулярку текстом я могу скопировать и вставить в условный regex101, чтобы её потестировать (или поправить). А такой код мне нужно частично исполнить, чтобы получить регулярку, которую можно тестировать. И после того, как я её потестирую, мне придётся исправления назад мучительно накатывать в код (с возможными ошибками в процессе).

Вот пример того, как длинная и сложная регулярка оформена в модуле fractions.py:
_RATIONAL_FORMAT = re.compile(r"""
    \A\s*                                 # optional whitespace at the start,
    (?P<sign>[-+]?)                       # an optional sign, then
    (?=\d|\.\d)                           # lookahead for digit or .digit
    (?P<num>\d*|\d+(_\d+)*)               # numerator (possibly empty)
    (?:                                   # followed by
       (?:\s*/\s*(?P<denom>\d+(_\d+)*))?  # an optional denominator
    |                                     # or
       (?:\.(?P<decimal>d*|\d+(_\d+)*))?  # an optional fractional part
       (?:E(?P<exp>[-+]?\d+(_\d+)*))?     # and optional exponent
    )
    \s*\Z                                 # and optional whitespace to finish
""", re.VERBOSE | re.IGNORECASE)


Эту регулярку можно как есть вставить в regex101 (нужно не забывать ставить флаг extended).
Вот так это выглядит:
Там, если нужно, можно отладить все аспекты работы регулярки (группы и т.п.), а потом как есть скопировать назад в код.

Собрать конкретно эту регулярку часто можно даже на этапе препроцессинга, не исключено, что даже плагин к vscode есть, который ее подсветит: все куски-то статичные. Ну а если не подсветит, всегда можно console.log впихнуть и посмотреть.

Ну, это такое. Если склейка регулярки из именованных кусков — это норм, я таки и вправду могу сунуть в console.log, то функции типа wholeInput — это провал.
Такие функции первые претендуют на то, чтобы выехать в какой-то общий для всех модуль.
И вот после этого, чтобы собрать регулярку в консоли, мне нужно искать по проекту, где живут эти функции, копировать их отдельно в консольку, после этого уже собирать регулярку.
Кроме того, хоть слова wholeInput довольно понятные, но регулярки очень формальный язык, в которых каждый символ может иметь большое значение. Если я такое увижу в коде, то мне придётся полезть посмотреть, что именно имел в виду автор. А то мало ли, может он исходил из того, что у регулярки ещё какие-то флаги не выставлены?
Если я сделаю так:
const reg = new RegExp(wholeInput('\d+'), 'gm'); // wholeInput же умная, наверное?
// [...'123\nasdf\n53'.matchAll(reg)] — это два match'а

то могу получить не тот результат, который ожидаю.

Странно, что никто не упомянул визуализаторы регулярок типа этого. Получается вполне понятная картинка. Так-то глаз уже набит, но пару раз приходилось пользоваться.

Визуализация

А если воспользоваться встроенными возможностями, чтобы сразу увидеть, какая группа для чего и заодно получить их по имени?

/^(?<day>0[1-9]|1[012])[- /.](?<month>0[1-9]|[12][0-9]|3[01])[- /.](?<year>(19|20)\d\d)$/

Вот это, кстати, очень правильно! Именованные группы много где поддерживаются?

В большинстве языков. Если про JS — то с ECMAScript 2018.

Вот тут и здесь про них и их поддержку. Думаю, есть смысл добавить абзац про них в основную статью.

Какой кошмар. Любят же некоторые на пустом месте всё усложнить и понавязывать другим правила.
А чтобы быть конструктивным, предложу альтернативное решение проблемы запутанных регэкспов в коде. Некое расширение для IDE, превращающее регэкспы в интерактивные объекты, по нажатию на которые открывается окошко с парсером и редактором (подобных инструментов создано достаточно). Почти уверен, что в каком-нибудь VS Code это точно уже реализовано.

Зачастую regexp это write-only code. Его не надо читать, в него надо верить.

Если есть недоверие -- переписать. Благо, однострочник.

Да, я знаю про https://emailregex.com/ вариации. Это использование неподходящего инструмента для неподходящей цели. Но ведь можно же!

Это уж совсем капитуляция перед задачей. По-моему, не должно быть write-only кода, ни в тестах, ни в регулярках, ни в css или xpath-селекторах, ни даже в bash и perl-скриптах, если их читают и запускают несколько раз несколько людей.

Это избегание постановки ненужных задач. Задача вообще решить бизнес-проблему.

Регулярка это хороший способ быстро (по времени разработчика) решить часть из них.

Обертка если и нужна, то нужна не над регуляркой, а над доменной областью. Тут как с ORM -- мы упрощаем доступ к стандартно-структурированным данным, получая одновременно гомогенность и гарантии минимального качества. Когда ORM начинают использовать чтобы конструировать произвольной сложности SQL запросы вместо использования SQL запросов -- это серьёзная протечка абстракции, а не "мы сделали SQL читаемым".

Так и здесь, регулярка -- это упрощение одного из шагов разбора выражения. Когда регулярка перерастает однострочник надо её выкидывать и декомпозировать в грамматику а не в монстроидное дерево вызовов функций.

А клиенту и не надо знать код приложения. Написание регулярок - задача программиста. А разбираться, как она работает будут тестировщики. Если там написана фигня- претензии к программисту. Я понимаю, что есть уж совсем маленькие компании, где нет отдельного штата тестировщиков, и отдел разработки занимается несколькими задачами одновременно. Тут программист может подложить свинью коллегам.

Не слишком понял, причем тут тестировщики. Я пока про нашу кодерскую песочницу и то, что нужно повышать понятность написанного кода.

Бывает нечто, о чем говорят: "смотри, вот это новое"; но это было уже в веках, бывших прежде нас. Эккл. 1:10

Сказать по правде, уже известное решение, с его действительно читабельным


const myRegex = SuperExpressive()
  .startOfInput
  .optional.string('0x')
  .capture
    .exactly(4).anyOf
      .range('A', 'F')
      .range('a', 'f')
      .range('0', '9')
    .end()
  .end()
  .endOfInput
  .toRegex();

выглядит куда более стройным и последовательным, чем все эти метания между константами и функциями. Но даже и оно как-то не особо популярно. РНР форк вообще не взлетел. О причинах можно гадать, но факт налицо — попытки улучшить регулярные выражения не находят отклика у программистов.

Неплохое, сейчас добавим! Тут, наверное, та же ситуация, что и с другими библиотеками – не хочется тащить ее в код, если в проекте регулярок меньше пары десятков, но вполне можно – если их много, а сменяющие друг друга программисты уже стонут.

а сменяющие друг друга программисты уже стонут
А привнесение еще одного синтаксиса еще одного не стандартного фреймворка вместо стандарта, стоны, конечно-же, прекратит )

Теперь есть порт на Python, если кому вдруг интересно.

Даже если вы декомпозируете пример с датой из начала статьи, конструкция (0[1-9]|[12][0-9]|3[01]) - это плохой и невкусно пахнущий код. Выковыряйте чиселку года вульгарным \d{1,4}

Это как раз-таки вполне здравый подход в данном случае в рамках применения регулярок, а ваши дополнительные проверки в коде как раз и есть плохой и невкусно пахнущий код.

Регулярки на то и регулярки, чтобы ими оставаться. А вы превратили относительно просто читаемое выражение в вырвиглазный псевдоскрипт, который малопереносим, да и сложен в проверке в каком-нибудь regex101. Вы с тем же успехом поучите математиков формулы писать. Вполне достаточно давать понятные имена самое переменной, использовать именованные группы или разбивку по строкам.

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

Вот если бы надо было проверить, что 988 год будет записан как 0988 или что 8 марта записывается не как 8.3, а как 08.03 - тогда да, это явно текстуальная вещь, регэкспы очень в тему.

это не численная проверка а минимизация срабатываний.

в тексте можно встретить много цифр самых разнообразных. задание 0[1-9]|[12][0-9]3[0-1] лучше чем \d\d просто потому, что минимизирует выкусывания не-дат. да, это не гарантия, это просто микрооптимизация, чтобы не кусать лишнего.

Это потому что вы смешали теплое с мягким. Тема была о декомпозиции регулярных выражений, и в рамках их применения, да, регулярка тут лучше, чем какой-то странный код, который по факту занимается валидацией, а не сопоставлением, прикидываясь, что этим и занимается. Валидация же может быть любой сложности и далеко выходить за рамки поиска.

Опять эти войны остроконечников с тупоконечниками... Ни к чему это. Оба подхода имеют право на жизнь. Да, приведенные в статье примеры (статичных по сути) регулярок нет смысла декомпозировать в переменные/функции. Но в некоторых сложных случаях без такой декомпозиции не обойтись, особенно когда некоторые данные для регулярок подтягиваются извне и/или когда построение регулярки зависит от разных условий, т.е. когда регулярку надо формировать динамически. Пример: yargy-парсер, основанный на правилах.

У меня именно в том пойнт, что в обычном коде, без матлингвистики или построения компиляторов все строчки читаются за 0.5 секунд, а регулярка внезапно читается пять минут. Если это так, с ней не все в порядке, и надо искать способы, чтобы она тоже читалась быстрее.

Если рассматривать регулярку как "функцию", коей она и является под капотом, то вполне нормально, что на осмысление может потребоваться более "0.5 секунд"

Но ведь было бы прекрасно, если бы можно было прочитать ее быстрее чем за пять минут, например, за 2.5 минуты?

Осмыслением регулярок должен заниматься профессиональный программист. А на деле, имеем то, что чуть менее чем полностью, этим занимается человек, бывший учителем русского языка и литературы, который отлаживает код с регулярками. Ему этот код, естественно, не понятен. В итоге говнокод превращается в говнокод в квадрате.

А нанять профи компания не может из-за бюджета и нехватки специалистов. И даже если компания наймёт его, есть проблема, что он просто уйдёт и надо снова искать человека, который разберётся с непонятным кодом.

Сугубо личное мнение (обрабатываю много текста регулярными выражениями и вообще их фанат):
Декомпозиция описанная в статье излишня. Она порождает нагромождение высокоуровневого кода над регулярным выражением.
В большинстве случаев даже для сложных регулярных выражений достаточно использовать именованные группы и форматирование отступами для того, чтобы минимизировать проблемы восприятия регулярных выражений.
Есть исключение: иногда нужно переиспользовать отдельные части регулярных выражений и иметь возможность менять шаблон в одной точке кода. Тогда лучше выделить часть шаблона в отдельную переменную и после этого включать эту часть в другое регулярное выражение через replace, чтобы не смешивать высокоуровневый синтаксис с регулярным выражением:

userID = "user\d+";
regex = "^USERID$";
regex = regex.replace( "USERID", userID );
Sign up to leave a comment.

Articles