Pull to refresh

Некоторые ошибки при написании регэкспов

Reading time4 min
Views14K
По мотивам переведенной статьи

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

Итак, несколько способов предохраниться от разочарования в этом мощном и прекрасном инструменте…



Ancient Egyptian Regexp
  • Используйте только те регэкспы, которые вы написали сами

    .

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

  • Запомните значения мета-символов в контексте



    Очень важно знать и помнить что означают основные символы и в каком контексте их применять. Например знак "^" в начале выражения и в перечислении "[..]" имеет разное значение.
    К сожалению усидчивость при заучивании таблиц исчезает после таблицы умножения и вбить себе в голову несколько таблиц из хелпа получается только в виде набитых шишек.

    Как признак того что вы помните и успешно применяете данное правило это правильное экранирование мета-символов. Такое [a-z0-9\.-] матерый регэкспщик не напишет. Потому что точка в перечислении всего лишь точка и ее не надо экранировать, зато дефис в перечислении — мета-символ. В данном примере дефис стоит в конце и правильно распознается как обычный символ, но достаточно другому копи-пастеру «улучшить» его, добавив еще один символ в перечисление, то в лучшем случае будет ошибка интервала, а в худшем получим непростой баг: например вот в этом [a-z0-9\.-\/] парсер воспримет "\.-\/" как интервал а не как 3 символа, как вроде-бы ожидалось.

  • Думайте как работает парсер. Старайтесь ему помочь.



    Конечно современные парсеры достаточно сложны что бы быть предметом общественного внимания, но в принципе они работают логично. Если вы написали подряд несколько паттернов то они и будут искаться подряд. И лучше всего так именно и делать — вписывать паттерны друг за дружкой и не забывать про разделители. Типа этого:
    /^<[^>]+>$/
    где в реальности три шаблона:
    • символ "<"
    • не ">" символов один или больше одного
    • символ ">"

    И вы должны давать парсеру как можно больше островков стабильности — конкретных символов «http», в крайнем случае ограничивайте перечислением "[\w\s]+" или давайте несколько вариантов "(http)|(ftp)". И чем меньше будет ".+" и "*" тем стабильнее и быстрее будет результат.

    если вдруг вы решите что так будет лучше:
    /^<.+>$/
    то скорее всего нарветесь на забавные проблемы. Поскольку стандартные имплементации по умолчанию «жадные» то внутри тега окажется все от начала первого тега, до конца последнего. Подробнее об этом чуть позже.

    Да и старайтесь избегать неопределенностей. Например
    /([a-z]+)([^<]+)*>/
    может разбить последовательность букв любым способом. Опять же, поскольку по-умолчанию квантификаторы «жадные» то в первый шаблон «правильно» попадет слово до первой не-буквы и остальное до ">" во-второй. Наверно правильнее было-бы написать
    /([a-z]+)([^a-z<>]*)>/
    но возвращаясь к первому правилу — это зависит от задачи.

  • Не используйте регулярные выражения для разбора с вложенностью



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

    Для примера возмем кусочек html:
    <span>text</span><span>text<span>text</span></span>
    как видно могут быть
    • вложенные теги
    • последовательные теги

    если с последовательными тегами можно разобраться с помощью «не жадных» квантификаторов или стопив выражение на начале следующего ( было выше типа "[^<]+" ), то добиться вложенности практически нереально. То есть для конкретных случаев это реализуемо, но всегда найдется вариант, который поломает нашу стройную картину.

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

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



    Возмем код «валидации» IP
    /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/

    зачем по-символьно разбирать числа? в данном примере будут «валидны» адреса 0.0.0.0 и 255.255.255.255, не говоря уже о прочих multicast-ах.
    В таких случаях лучше делать простой фильтр на откровенно невалидный ввод ( типа SQL injection ) /^\d+\.\d+\.\d+\.\d+$/ а уж что там за IP — разобрать отдельно



Обычно такие статьи называются типа «10 поз которые должен знаться каждый»… я намеренно заменил <ol> на <ul> потому что я не Бог, я только учусь.
Я написал исключительно то что помню и на что нарывался. Думаю что каждый со сравнимым опытом может продемонстрировать свои шишки.
Tags:
Hubs:
+88
Comments45

Articles

Change theme settings