Ошибка входа, вызванная несоответствием форматов хранения номеров телефонов, выявила серьезный изъян. Аккаунт одного пользователя был заблокирован, потому что в базе данных один и тот же немецкий мобильный номер хранился в двух видах: 0171 5550134 в одной строке и +49 171 5550134 в другой — из-за этого система воспринимала их как разные записи. Итог: OTP-код так и не пришел, а кнопка «отправить код» превратилась в лотерею.

Почему номера телефонов сложнее дат

Разработчики часто полагаются на регулярные выражения (regex), чтобы обуздать ввод номеров телефонов. Но это доверие рушится, как только номер пересекает границу или меняется национальный план нумерации. Даты следуют предсказуемому календарю; номера телефонов же постоянно меняются вместе с операторами, правилами и культурными особенностями.

Скрытая цена хранения в виде обычных строк

Хранение номера телефона в виде обычной строки кажется надежным — пока две разные записи не начинают указывать на одну и ту же линию. Системы OTP, которые запрашивают в базе данных «тот самый» номер, в итоге отправляют код на дубликат, который никогда не дойдет до пользователя.

Проблема усугубляется, когда номера хранятся в числовых полях. Столбец типа BIGINT отсекает знак + и любые ведущие нули, превращая +49 171 5550134 в 491715550134. Без знака «плюс» восстановление исходного формата превращается в гадание.

Контракт E.164

Международный план нумерации телефонов E.164 определяет единое и переносимое представление:

  • Начинается с +
  • Далее следует 1–3-значный код страны
  • Затем номер абонента
  • Всего не более 15 цифр
  • Никаких пробелов, точек или тире

E.164 не гарантирует, что номер активен; он лишь гарантирует, что строка соответствует правильному структурному шаблону. Относитесь к нему как к контракту формата, а не как к оракулу доступности.

Распространенные ловушки, которые не исправить регулярными выражениями

  • Числовое хранениеBIGINT отбрасывает + и ведущие нули. Вместо этого используйте текстовый столбец (TEXT или VARCHAR).
  • Жестко заданные regex — национальные планы меняются. Мексика отказалась от своего магистрального префикса в 2019 году; Аргентина теперь требует цифру 9 после кода страны для мобильных линий. Статический шаблон быстро устаревает.
  • Слепое удаление нулей — в итальянских стационарных номерах сохраняется ведущий ноль, в немецких — нет. Универсальное правило «удалять ведущие нули» портит итальянские данные, не затрагивая немецкие номера.
  • Убеждение, что формат означает доступностьlibphonenumber проверяет структуру, но не может сказать, включен ли телефон или был ли перенесен номер к другому оператору.

Построение надежного конвейера данных

  1. Запрашивайте страну — добавьте селектор страны в формы регистрации и передавайте этот регион парсеру.
  2. Показывайте форматирование в реальном времени — используйте форматирование «по мере ввода» (AsYouType), чтобы пользователи видели правильный шаблон прямо во время набора.
  3. Валидируйте при потере фокуса (on blur) — запускайте валидацию после того, как пользователь покидает поле, а не при каждом нажатии клавиши; это снижает раздражение.
  4. Сохраняйте только строку в формате E.164 — храните в базе данных нормализованный номер с префиксом +.
  5. Форматируйте на «границе» — возвращайте номер к удобному для человека виду только в пользовательском интерфейсе или шаблонах писем.

При миграции устаревших данных сохраняйте строки, которые не прошли валидацию. Распарсите каждую существующую запись в новый столбец, пометьте ошибки и сформируйте отчет. «Тихое» удаление данных порождает обращения в службу поддержки, которые позже превращаются в дорогостоящие исправления.

Краткий чек-лист для разработчиков

  • Храните номера в формате E.164, используя типы TEXT/VARCHAR.
  • Используйте библиотеку Google libphonenumber; она справляется со всей сложностью глобальных планов нумерации.
  • Предоставляйте регион по умолчанию для пользователей, которые не указывают код страны.
  • Вызывайте is_valid_number при регистрации пользователей; это проверяет соответствие номера региональным правилам.
  • Используйте is_possible_number при очистке больших массивов данных; это позволяет отсеивать явно некорректные записи, не отклоняя пограничные случаи.

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