یک شکست در ورود به سیستم که ریشه در عدم تطابق ذخیره‌سازی شماره تلفن داشت، یک نقص را آشکار کرد. حساب کاربری یکی از کاربران مسدود شد زیرا پایگاه داده یک شماره موبایل آلمانی را به دو شکل ذخیره کرده بود: 0171 5550134 در یک ردیف و +49 171 5550134 در ردیفی دیگر؛ بنابراین سیستم آن‌ها را به عنوان ورودی‌های مجزا در نظر گرفت. نتیجه: کد OTP هرگز ارسال نشد و دکمه «ارسال کد» به یک قمار تبدیل شد.

چرا شماره‌های تلفن سخت‌تر از تاریخ‌ها هستند

توسعه‌دهندگان اغلب به یک regex اعتماد می‌کنند تا ورودی شماره تلفن را مهار کنند. این اعتماد لحظه‌ای که یک شماره از مرزی عبور می‌کند یا یک طرح ملی تغییر می‌کند، از هم می‌پاشد. تاریخ‌ها از یک تقویم قابل پیش‌بینی پیروی می‌کنند؛ اما شماره‌های تلفن با تغییر اپراتورها، مقررات و قراردادهای فرهنگی تغییر می‌کنند.

هزینه پنهان رشته‌های خام (raw strings)

ذخیره کردن شماره تلفن به عنوان یک رشته ساده (plain string) منحصر‌به‌فرد به نظر می‌رسد—تا زمانی که دو نمایش متفاوت به یک خط اشاره کنند. سیستم‌های OTP که برای یافتن «آن» شماره در پایگاه داده جستجو می‌کنند، در نهایت کد را به یک نسخه تکراری می‌فرستند که هرگز به دست کاربر نمی‌رسد.

مشکل زمانی بدتر می‌شود که شماره‌ها در فیلدهای عددی قرار می‌گیرند. یک ستون BIGINT علامت + و صفر‌های ابتدایی را حذف می‌کند و +49 171 5550134 را به 491715550134 تبدیل می‌کند. بدون علامت مثبت، بازسازی فرمت اصلی به یک حدس و گمان تبدیل می‌شود.

قرارداد E.164

طرح بین‌المللی شماره‌گذاری تلفن، E.164، یک نمایش واحد و قابل حمل را تعریف می‌کند:

  • با یک + شروع می‌شود
  • به دنبال آن یک کد کشور ۱ تا ۳ رقمی می‌آید
  • سپس شماره مشترک
  • در مجموع حداکثر ۱۵ رقم
  • بدون فاصله، نقطه یا خط تیره

E.164 تضمین نمی‌کند که شماره فعال است؛ بلکه فقط تضمین می‌کند که رشته از یک الگوی ساختاری معتبر پیروی می‌کند. با آن به عنوان یک قرارداد فرمت برخورد کنید، نه یک پیشگوی قابلیت دسترسی.

تله‌های رایجی که regex نمی‌تواند آن‌ها را اصلاح کند

  • ذخیره‌سازی عددیBIGINT علامت + و صفرهای ابتدایی را دور می‌ریزد. در عوض از یک ستون متنی (TEXT یا VARCHAR) استفاده کنید.
  • regexهای ثابت (Hard-coded) – طرح‌های ملی تغییر می‌کنند. مکزیک در سال ۲۰۱۹ پیشوند اصلی (trunk prefix) خود را حذف کرد؛ آرژانتین اکنون برای خطوط موبایل پس از کد کشور به 9 نیاز دارد. یک الگوی ایستا به سرعت منسوخ می‌شود.
  • حذف کورکورانه صفرها – تلفن‌های ثابت ایتالیا صفر ابتدایی را حفظ می‌کنند، اما تلفن‌های آلمانی خیر. یک قانون کلی برای «حذف صفرهای ابتدایی»، داده‌های ایتالیایی را خراب می‌کند در حالی که شماره‌های آلمانی را بدون تغییر باقی می‌گذارد.
  • فرض اینکه فرمت برابر با قابلیت تحویل است – کتابخانه libphonenumber ساختار را اعتبارسنجی می‌کند اما نمی‌تواند تشخیص دهد که آیا گوشی روشن است یا شماره منتقل (port) شده است.

ساخت یک خط لوله (pipeline) قابل اعتماد

  1. درخواست کشور – یک انتخاب‌گر کشور در فرم‌های ثبت‌نام اضافه کنید و آن منطقه را به پارسر (parser) ارسال کنید.
  2. نمایش فرمت زنده – از فرمت‌بندی “AsYouType” استفاده کنید تا کاربران الگوی صحیح را هنگام تایپ مشاهده کنند.
  3. اعتبارسنجی در هنگام خروج (on blur) – اعتبارسنجی را پس از اینکه کاربر فیلد را ترک کرد انجام دهید، نه با هر کلید؛ این کار اصطکاک را کاهش می‌دهد.
  4. فقط رشته E.164 را ذخیره کنید – شماره نرمال‌شده با پیشوند + را در پایگاه داده ذخیره کنید.
  5. فرمت‌بندی در لبه (at the edge) – تبدیل به یک چیدمان کاربرپسند را فقط در رابط کاربری (UI) یا قالب‌های ایمیل انجام دهید.

هنگام مهاجرت داده‌های قدیمی (legacy data)، ردیف‌هایی که در اعتبارسنجی شکست می‌خورند را نگه دارید. هر ورودی موجود را در یک ستون جدید تجزیه (parse) کنید، شکست‌ها را علامت‌گذاری کنید و یک گزارش ایجاد کنید. حذف بی‌صدای داده‌ها باعث ایجاد تیکت‌های پشتیبانی می‌شود که بعداً به اصلاحات پرهزینه تبدیل می‌شوند.

چک‌لیست سریع برای توسعه‌دهندگان

  • شماره‌ها را به صورت TEXT/VARCHAR در فرمت E.164 ذخیره کنید.
  • از کتابخانه libphonenumber گوگل استفاده کنید؛ این کتابخانه واقعیت‌های پیچیده طرح‌های جهانی را مدیریت می‌کند.
  • برای کاربرانی که کد کشور را وارد نمی‌کنند، یک منطقه پیش‌فرض در نظر بگیرید.
  • برای ثبت‌نام‌های زنده از is_valid_number استفاده کنید؛ این تابع بررسی می‌کند که آیا شماره با قوانین منطقه‌ای مطابقت دارد یا خیر.
  • هنگام پاکسازی داده‌های انبوه از is_possible_number استفاده کنید؛ این تابع ورودی‌های آشکارا ناقص را بدون رد کردن موارد مشکوک، شناسایی می‌کند.

مدیریت صحیح شماره تلفن یک ویژگی «خوب اگر داشته باشیم» نیست؛ بلکه پیش‌نیاز هر سیستمی است که بر تماس قابل اعتماد با کاربر تکیه دارد. با نرمال‌سازی به E.164 و سپردن تجزیه (parsing) به یک کتابخانه آزموده شده، توسعه‌دهندگان دسته‌ای از باگ‌ها را که به آرامی اعتماد و درآمد را از بین می‌برند، حذف می‌کنند. با شماره‌های تلفن به عنوان داده‌های ساختاریافته برخورد کنید، نه متن آزاد، و اجازه دهید استانداردها کارهای سنگین را انجام دهند.