خلاصهی پاسخ: کدی که قرار است انسان از روی تصویر بخواند و تایپ کند، باید ذاتاً خطا را تشخیص بدهد. کد ۶ رقمی متشکل از ۵ رقم داده و ۱ رقم کنترلی لان، هر خطای تکرقمی را ۱۰۰٪ میگیرد و بیشتر جابهجاییهای دو رقم مجاور را هم؛ ظرفیت ۱۰۰٬۰۰۰ کد بهازای هر فروشگاه دارد و کوتاهتر از آن است که تایپکردنش برای مشتری هزینه باشد.
مسئله را دقیق کنیم
ما یک کانال ارتباطی داریم که فرستندهاش تصویر است، گیرندهاش چشم انسان و مجرای انتقالش صفحهکلید موبایل. مثل هر کانال دیگری، نویز دارد: رقم بد دیده میشود، انگشت روی رقم کناری میخورد، دو رقم جابهجا تایپ میشوند. سه نوع خطای غالب در ورود دستی ارقام، در ادبیات تشخیص خطا شناختهشدهاند:
- خطای تکرقمی (single-digit error): یک رقم اشتباه — بیشترین فراوانی.
- جابهجایی مجاور (adjacent transposition): «۴۷» بهجای «۷۴».
- خطای دوقلو و پرش: تکرار یا حذف رقم.
بدون رقم کنترلی، هر خطای تکرقمی با احتمال بالا به یک کد معتبر دیگر تبدیل میشود. یعنی مشتری بدون هیچ هشداری وارد صفحهی محصول اشتباه میشود — بدترین حالت ممکن، چون سیستم مطمئن بهنظر میرسد و اشتباه است.
چرا لان و نه فرهوف یا دام؟
سه الگوریتم کلاسیک رقم کنترلی دهدهی وجود دارد:
| الگوریتم | خطای تکرقمی | جابهجایی مجاور | پیچیدگی پیادهسازی |
|---|---|---|---|
| لان (Luhn) | ۱۰۰٪ | اکثر موارد (بهجز ۰۹/۹۰) | چند خط، بدون جدول |
| فرهوف (Verhoeff) | ۱۰۰٪ | ۱۰۰٪ | سه جدول ۱۰×۱۰ (گروه دووجهی) |
| دام (Damm) | ۱۰۰٪ | ۱۰۰٪ | یک جدول شبهگروه ۱۰×۱۰ |
از نظر قدرت تشخیص، فرهوف و دام برترند. اما انتخاب ما لان بود و دلیلش مهندسی است، نه ریاضی: کد محصول علاوه بر سرور، در مرورگر مشتری هم پیش از ارسال فرم بررسی میشود تا خطا در همان لحظه و بدون رفتوبرگشت شبکه دیده شود. لان بدون هیچ جدولی در چند خط جاوااسکریپت پیاده میشود، ریسک ناهماهنگی بین پیادهسازی سمت سرور و سمت مرورگر را کمینه میکند و همان الگوریتمی است که میلیاردها شمارهی کارت بانکی با آن بررسی میشوند — یعنی سطح آزمونشدگیاش در دنیای واقعی بیرقیب است. خطای پوششدادهنشدهی لان (جابهجایی ۰۹↔۹۰) در فضای ۵ رقمی، سهم ناچیزی از خطاهاست و در ازای سادگی پیادهسازی پذیرفته شد.
آزمون عددی، نه ادعا
طراحی را با آزمون خودکار سنجیدیم: ۲۰۰ کد تصادفی تولید شد و روی هر کد، تکتک ۶ موقعیت رقمی با هر ۹ رقم جایگزین ممکن تغییر داده شد — یعنی ۲۰۰×۶×۹ = ۱۰٬۸۰۰ جهش تکرقمی. هر ۱۰٬۸۰۰ مورد بهعنوان کد نامعتبر تشخیص داده شد. این همان تضمین نظری لان است، اما تفاوت بین «باید کار کند» و «روی کد ما کار میکند» را همین آزمون میسازد.
چرا ۵ رقم داده؟ محاسبهی ظرفیت و حدسزدن
۵ رقم داده یعنی ۱۰۰٬۰۰۰ کد ممکن. برای یک فروشگاه با چند هزار محصول، فضای مصرفشده کسر کوچکی از فضای کل است و چون کدها تصادفی تخصیص داده میشوند (نه ترتیبی)، حدسزدن کد محصول بعدی از روی کد فعلی ممکن نیست. این نکته امنیتی مهمی است: کد ترتیبی، فهرست محصولات و حتی نرخ رشد کاتالوگ شما را برای رقیب لو میدهد.
در عین حال باید صریح بود: این کد یک شناسه است، نه یک راز. کد فقط به صفحهی عمومی محصول میرسد؛ هیچ عملیات حساسی پشت آن نیست. جایی که کد به رکورد خصوصی وصل میشود (مثلاً بازگشت به سبد نیمهکاره)، مسیر با توکن جداگانه محافظت میشود، نه با خودِ کد.
مسئلهای که در متن انگلیسی اصلاً وجود ندارد: ارقام فارسی و متن دوجهته
وقتی کد را داخل یک کپشن فارسی میگذارید یا روی تصویر راستبهچپ مینویسید، دو تلهی واقعی سر راه است.
۱) نرمالسازی ارقام ورودی
مشتری ممکن است کد را با صفحهکلید فارسی (۰۱۲۳)، عربی (٠١٢٣) یا لاتین (0123) تایپ کند. اگر ورودی را خام مقایسه کنید، کد درست هم رد میشود. راهحل: پیش از هر بررسی، همهی ارقام به لاتین نرمال شوند — هم در مرورگر و هم روی سرور، با یک تابع مشترک.
PERSIAN = "۰۱۲۳۴۵۶۷۸۹"
ARABIC = "٠١٢٣٤٥٦٧٨٩"
def normalize(raw):
out = []
for ch in raw:
if ch in PERSIAN:
out.append(str(PERSIAN.index(ch)))
elif ch in ARABIC:
out.append(str(ARABIC.index(ch)))
elif ch.isdigit():
out.append(ch)
return "".join(out)
۲) ترتیب نمایش در متن راستبهچپ
الگوریتم دوجهتهی یونیکد (UAX #9) تعیین میکند اعداد داخل متن راستبهچپ چطور بازچینش شوند. اثر عملیاش این است که اگر کد را برای خوانایی گروهبندی کنید — مثلاً «۴۸۲ ۹۱۷» — ممکن است در متن فارسی بهصورت «۹۱۷ ۴۸۲» رندر شود و مشتری کد را وارونه بخواند. این باگ را روی تصویر واقعی دیدیم، نه در تئوری.
دو تصمیم آن را حل کرد: گروهبندی ارقام بهصورت پیشفرض خاموش است، و کد همیشه داخل جداکنندههای ایزولهی دوجهته (LRI و PDI) چاپ میشود تا هیچ متن اطرافی نتواند ترتیبش را عوض کند.
۳) شکلدهی حروف هنگام حک روی تصویر
برای نوشتن متن فارسی روی تصویر با Pillow دو دنیا وجود دارد: اگر کتابخانه با پشتیبانی Raqm ساخته شده باشد، خودش شکلدهی حروف را با HarfBuzz و ترتیب دوجهته را با FriBidi انجام میدهد و باید متن را منطقی (نه ازپیشبازچینششده) به آن بدهید. اگر Raqm نباشد، باید خودتان متن را شکلدهی و بازچینش کنید. اشتباه رایج — و باگی که ما هم گرفتیم — انجام هر دو کار با هم است: نتیجهاش متنی است که دوبار شکل داده شده و حروفش بههم میریزد. راه درست: در زمان اجرا وجود Raqm را تشخیص بدهید و فقط یکی از دو مسیر را بروید.
کد را کجای تصویر بگذاریم؟
خوانایی کد یک مسئلهی طراحی بصری است و مستقیم روی نرخ ورود کد اثر میگذارد. چند قاعدهی عملی که بهصورت پیشتنظیم در شاپ اینستاگرام ابرک پیاده شدهاند:
- نوار پسزمینهی نیمهشفاف زیر کد، تا روی هر تصویری کنتراست تضمین شود.
- گوشهی پایین، دور از لبه: بخشی از تصویر که در نمای شبکهای پروفایل و در تمام نسبتهای تصویر باقی میماند.
- اندازهی قلم متناسب با عرض تصویر، نه اندازهی ثابت — کد باید در پیشنمایش کوچک فید هم خوانده شود.
- تکرار کد در کپشن و ترجیحاً در خط آخر: بسته به جایی که کپشن بریده میشود، دستکم یکی از دو تکرار دیده میشود.
جمعبندی
یک کد ۶ رقمی ساده بهنظر میرسد، ولی چهار تصمیم مستقل در آن نهفته است: طول و ظرفیت، الگوریتم تشخیص خطا، رفتار در برابر ارقام غیرلاتین و متن دوجهته، و طراحی بصری روی تصویر. اشتباه در هرکدام، خودش را بهصورت «مشتری به محصول نرسید» نشان میدهد — نه بهصورت خطای برنامه. برای دیدن جای این کد در کل مسیر فروش، مقالهی شکاف فروشگاه اینستاگرام و برای انتشار خودکار پستِ حاوی همین کد، مقالهی انتشار خودکار را ببینید.