۳. قرارداد داده؛ اعتبارسنجی مبلغ و ورودی فارسی
کاربر مبلغ «۲۵۰۰۰۰» را وارد میکند. فرم متن تحویل میدهد، اما مدل دفتر هزینه عدد صحیح تومان میخواهد. تبدیلکردن مستقیم هر ورودی با Number قرارداد مناسبی نیست: رشتهٔ خالی به صفر تبدیل میشود و نمایشهایی مانند نماد علمی ممکن است پذیرفته شوند، درحالیکه طراح فرم فقط رقم ساده میخواسته است. در سطح متوسط، ابتدا قرارداد قابل توضیح مینویسیم و سپس آن را پیاده میکنیم.
قرارداد این درس چنین است: ورودی متن، رقم فارسی یا عربی یا لاتین پذیرفته، فاصلهٔ ابتدا و انتها قابل حذف، ولی جداکننده، اعشار، علامت و نماد علمی رد میشود. خروجی عدد صحیح بین یک تا یک تریلیون تومان است. محدودیت مقدار، تصمیم آموزشی این پروژه است و قانون عمومی همهٔ سامانههای مالی نیست.
عادیسازی محدود، سپس بررسی شکل
function parseToman(raw) {
if (typeof raw !== 'string') throw new TypeError('Expected text');
const fa = '۰۱۲۳۴۵۶۷۸۹';
const ar = '٠١٢٣٤٥٦٧٨٩';
const text = raw.trim()
.replace(/[۰-۹]/g, digit => String(fa.indexOf(digit)))
.replace(/[٠-٩]/g, digit => String(ar.indexOf(digit)));
if (!/^\d{1,13}$/.test(text)) throw new Error('Use plain integer digits');
const value = Number(text);
if (!Number.isSafeInteger(value) || value < 1 || value > 1_000_000_000_000) {
throw new RangeError('Amount out of range');
}
return value;
}
console.log(parseToman(' ۲۵۰۰۰۰ ')); // 250000
console.log(parseToman('٣٠٠٠٠')); // 30000
for (const input of ['', '1e3', '1,000', '۱۲.۵', '۰']) {
try { parseToman(input); }
catch { console.log('رد شد:', input); }
}در فایل پروژه، تابع parseAmount همین نقش را با پیامهای فارسی دارد. مثال بالا را با آن مقایسه کنید؛ هدف نام تازه نیست، هدف فهمیدن ترتیب تصمیمهاست. عادیسازی فقط رقمهای شناختهشده را تغییر میدهد. حذف همهٔ نویسههای غیرعددی کار خطرناکی است: «۱۲-۳» نباید بیخبر به ۱۲۳ تبدیل شود. کاربر باید خطای ورودی را ببیند و آن را اصلاح کند.
عدد صحیح امن به این معنی نیست که حاصل همهٔ عملیات بعدی هم امن خواهد ماند. در پروژه مجموع مبلغها دوباره بررسی میشود. همچنین عدد صحیح تومان راهحل اختصاصی این واحد است؛ برای ارزی با جزء کوچکتر، واحد پایه و سیاست گردکردن باید جداگانه تعریف شود. تبدیل نمایشی تومان به ریال را نیز بیاعلام انجام ندهید.
قرارداد شیء و حذف فیلدهای اضافی
دادهٔ دریافتشده از فایل را مستقیماً به مدل اضافه نکنید. فقط فیلدهای مجاز را بسازید. این مثال یک ردیف سادهٔ گزارش را بررسی میکند و عمداً قرارداد کامل شناسه و تاریخ پروژه را پیاده نمیکند:
function cleanReportRow(input) {
if (!input || typeof input !== 'object' || Array.isArray(input)) {
throw new TypeError('Expected an object');
}
if (typeof input.title !== 'string') throw new Error('Title required');
const title = input.title.trim();
if (title.length < 2 || title.length > 100) throw new Error('Invalid title');
if (!Number.isSafeInteger(input.amount) || input.amount < 1) {
throw new Error('Invalid stored amount');
}
return { title, amount: input.amount };
}
const clean = cleanReportRow({ title: ' کتاب ', amount: 250000, admin: true });
console.log(clean); // { title: 'کتاب', amount: 250000 }
console.log('admin' in clean); // falseورودی فرم مبلغ را به شکل متن میفرستد؛ فایل پشتیبانِ قرارداد فعلی باید مبلغ عددی داشته باشد. بهتر است این دو ورودی توابع جدا داشته باشند، تا یک تغییر آسانگیرانه در فرم ناخواسته اعتبارسنجی فایل را ضعیف نکند. در validateExpense پروژه، دسته، شناسه و تاریخ نیز بررسی میشوند؛ بعداً در بازیابی پشتیبان، تکرارینبودن شناسهها و مجموع نیز کنترل میشود.
نمایش با ذخیره فرق دارد
برای نمایش، از Intl.NumberFormat('fa-IR') استفاده کنید، اما عدد قالببندیشده را جای مقدار مدل ذخیره نکنید. «۲۵۰٬۰۰۰ تومان» متن نمایشی است و تابع جمع نباید آن را تجزیه کند. همین تفکیک دربارهٔ تاریخ مهم است: فرم آموزشی تاریخ استاندارد را نگه میدارد و نمایش فارسی میتواند لایهای جدا باشد. ساختن Date بهتنهایی برای پذیرفتن تاریخ کافی نیست؛ برخی ورودیها ممکن است عادیسازی شوند، بنابراین قرارداد باید تاریخ ناموجود را نیز رد کند.
پیام خطا باید به عمل بعدی کمک کند. بهجای «نامعتبر»، بنویسید «مبلغ را فقط با رقم، بدون ممیز و جداکننده وارد کنید». مقدار تایپشده را پس از خطا پاک نکنید. در رابط، فیلد خطادار را مشخص کنید؛ در سرور، همین بررسیهای لازم را مستقل انجام دهید. اعتبارسنجی مرورگر تجربهٔ کاربری را بهتر میکند و جای کنترل مرز سرور را نمیگیرد.
قرارداد باید نزدیک محل استفاده نوشته شود. در توضیح فرم بنویسید واحد مبلغ تومان است و جداکننده پذیرفته نمیشود. اگر بعداً تصمیم گرفتید جداکننده را بپذیرید، ابتدا نمونههای مجاز و مبهم را تعریف کنید، سپس تابع تبدیل و آزمونها را تغییر دهید. تغییر پیام راهنما بدون تغییر قرارداد یا برعکس، کاربر را سردرگم میکند. همچنین برای ورودی بسیار بلند، محدودیت طول در فرم مفید است، ولی بررسی تابع همچنان باید مستقل باقی بماند.
تمرین، پاسخ و معیار تحویل
برای parseToman جدولی با دستکم ده ورودی بسازید: ارقام مخلوط، فاصلهٔ ابتدا، صفر، منفی، مقدار بیش از سقف، آرایه، عدد واقعی، متن حروفی و مبلغ درست. پیش از اجرا خروجی یا نوع شکست هر مورد را بنویسید. سپس یک فرم با حفظ ورودی نامعتبر و پیام فارسی بسازید.
پاسخ راهنما: نوع ورودی پیش از trim بررسی میشود، سپس عادیسازی، بررسی شکل و محدوده انجام میگیرد. ورودی 250000 عددی در این مرز رد میشود، چون قرارداد متن فرم است؛ این ردشدن خطا نیست. معیار تحویل، پذیرفتن همهٔ حالتهای مجاز و ردشدن مستند حالتهای غیرمجاز بدون تبدیل خاموش به صفر است. در مرور کد، دلیل جداکردن تابع تبدیل فرم از اعتبارسنجی دادهٔ ذخیرهشده را توضیح دهید.
منابع رسمی برای مطالعهٔ تکمیلی
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/Number
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat
تألیف و مثالهای اختصاصی: تحریریه درسآوند.