مطالعه موردی مهندسی
وفاداری و دفترکل کیف پول
موجودی وفاداری پول است. در یک کسبوکار طلا و جواهر این پول از چند راه میآید: بیعانه روی یک قطعه، اعتبار حاصل از معاوضه، موجودی هدیهی یک کمپین. هر نقص در کدی که این پول را جابهجا میکند، یک مبلغ واقعی است: یا به مشتری بدهکار میشوی یا از او کم میکنی. برای همین اول دامنه را ساختم و جداگانه اثباتش کردم، پیش از آنکه حتی یک هوک وردپرس یا یک جدول دیتابیس وجود داشته باشد.
مسئله تجاری
بیشتر پیادهسازیهای وفاداری موجودی را در یک ستون نگه میدارند و به آن اضافه میکنند. تا اولین بازپرداخت، اولین پسگیری یا اولین اختلاف بر سر اینکه شش ماه پیش چقدر به مشتری بدهکار بودهایم، همین کافی است. از آن نقطه به بعد دیگر نمیشود بازسازی کرد آن عدد چطور درآمده. جواهر کار را از دو جهت سختتر میکند. ارزش مشتری اینجا یکجور نیست: بیعانهی یک قطعهی مشخص جای دیگری خرج نمیشود، اعتبار معاوضه شاید بشود، موجودی کمپین تاریخ انقضا دارد. ریختن همه در یک عدد، حساب را ساده میکند و پرسشها را بیجواب، و دقیقاً همین پرسشها سر اختلافها بالا میآیند. و چون کالا گرمی قیمت میخورد، حساب اعشاری خطای گرد کردن نیست، کسری است: یک کسب امتیاز و برگشتش دیگر دقیقاً همدیگر را خنثی نمیکنند، و آن خطا برای همیشه میماند.
آنچه تحویل دادم
- یک نوع Money با واحدهای صحیح خرد ریال. اعشار را بهجای گرد کردن بیصدا رد میکند و در هر دو جهت بهسمت صفر گرد میکند، تا یک کسب و پسگیریاش دقیقاً همدیگر را صفر کنند.
- یک دفتر کیف پول فقط-افزودنی که هر حرکت در آن یک رکورد با نوع مشخص خودش است و موجودی جمع همین رکوردهاست، نه یک ستون ذخیرهشده. هیچچیز ویرایش یا حذف نمیشود؛ هر اصلاح یک رکورد برگشت است که میگوید کدام رکورد را خنثی میکند، و همین تاریخچه را صادق نگه میدارد.
- سطلهای جدا و نوعدار برای اعتبار نقدی، اعتبار تبلیغاتی و اعتبار بازپرداخت، قابل کوئری و نه چپانده در یک فیلد یادداشت؛ این سه از نظر مالیات، وضعیت حقوقی و قواعد انقضا فرق دارند و نمیشود در یک موجودی جمعشان کرد. اعتبار صادرشدهی فروشگاه اول خرج میشود تا فروشگاه هیچوقت هدیهی خودش را بهجای پول مشتری پس ندهد.
- چرخهی حیات امتیاز: امتیاز غیرقابلخرج ضرب میشود و تا وقتی سفارشِ سازندهاش برگشتناپذیر نشده، همانطور میماند. امتیاز لغوشده و منقضیشده هم از هم جدا نگه داشته میشوند؛ منقضی واقعاً کسب شده و کهنه شده، لغوشده هیچوقت کسب نشده بود.
- یک مبنای کسب صریح: پس از تخفیف، پیش از مالیات، پیش از حمل، با استثناهای نوعدار. ارزش یک سفارش برای کسب امتیاز فقط در یک جا تعریف شده است.
- ردپای ممیزی که از خود طراحی درمیآید، نه یک لاگ جدا. خودِ رکوردها سابقهاند، پس هر موجودی بهشکل زنجیرهای از حرکتها همراه با علتشان خوانده میشود؛ بدون این، گفتوگو با مشتری دربارهی موجودیاش ممکن نیست.
- یک بررسیکنندهی جهش که نقصهای پولی شناختهشده را یکییکی داخل کد میکارد و اگر یکی از آنها از تستها جان سالم به در ببرد، شکست میخورد.
رویکرد فنی
- دامنه کامل ساخته و اثبات شد، پیش از آنکه هیچ ذخیرهسازی، اندپوینت، هوک یا کرونی وجود داشته باشد. هیچچیز اینجا نصبشدنی نیست و اصلاً دلیل کار همین بود: منطق پول باید پیش از در دسترس بودن، درست باشد.
- موجودی همیشه محاسبه میشود، هیچوقت ذخیره نمیشود. خواندنش گرانتر تمام میشود، ولی فقط در همین ساختار است که جواب «چطور به اینجا رسیدیم» اصلاً وجود دارد.
- تفکیک سطلها مدل شده، نه قراردادی. اعتبار تبلیغاتیِ منقضیشونده و اعتبار نقدیِ بیانقضا دو چیز متفاوتاند؛ یک ستون موجودیِ واحد مجبورشان میکند خودشان را یکی جا بزنند. دستهای که نشود کوئریاش کرد، بالاخره اشتباه ثبت میشود.
- امتیاز غیرقابلخرج ضرب میشود، چون حالت دیگر این است: مشتری امتیاز سفارشی را خرج کند که بعداً بازپرداخت میشود. آن یک زیان واقعی است و جبرانش سخت. همین یک قاعده چرخهی لغو و رد کردن سفارشهای پسکرایه را هم میبندد، چون چیز قابلخرجی نیست که با آن در بروند.
- هر رزرو انحصاری مهلت دارد. وگرنه بازخریدی که فرایندش وسط راه بمیرد، امتیاز مشتری را تا ابد رزرو نگه میدارد: نه خرجشدنی، نه در موجودی قابلمشاهده، نه قابل توضیح.
- بررسیکنندهی جهش هست چون تست سبز فقط ثابت میکند کد از تستهای خودش رد میشود، نه اینکه تستها یک نقص واقعی را میگیرند. این ابزار چهار نقص را میکارد: گرد کردن رو به بالا در کسب، خرجکردن اعتبار نقدی پیش از تبلیغاتی، ضرب امتیازِ از همان اول قابلخرج، و بازخرید یک اعطای منقضیشده.
نتیجه و شواهد
بررسیکنندهی جهش تا الان چهار بار این کد را عوض کرده: دو گارد کد مرده و دستنیافتنی بودند، یکی یک محدودکنندهی بیصدا بود و همان باگی را پنهان میکرد که ظاهراً جلویش را میگرفت، و یکی علت اشتباه را گزارش میکرد. هر چهارتا هم از بازبینی رد میشدند و هم از تستها. دو گارد دیگر هم معلوم شد عمداً تکراریاند؛ بهجای حذف یا وانمود کردن، این تکرار صریحاً تثبیت شده و اگر لایهبندی روزی عوض شود، بررسیکننده آن را بهعنوان یافته گزارش میکند. و ارزش مشتری قابل توضیح شد: هر موجودی را میشود رکورد به رکورد تا مبدأش عقب برد، و وقتی مشتری با عددی مخالف است دقیقاً همین به کار میآید.
اهمیت برای کارفرما
سیستم وفاداریای که نتواند یک موجودی را توضیح دهد، اول بار پشتیبانی میشود و بعد پروندهی اختلاف. در خردهفروشی جواهر، بحث بر سر موجودی کم پیش میآید ولی رقمش بزرگ است؛ یعنی دقیقاً همانجایی که نمیشود باخت، و دفتر قابل ممیزی همان چیزی است که موضع فروشگاه را قابل دفاع میکند. درست ساختن دفتر از همان اول، ارزانتر از بازسازی آن بعد از یک ماه موجودی زنده است.
خلاصه اجرایی خوانا
خلاصه_اجرایی {
پروژه: "Loyalty + Wallet Ledger"
وضعیت: "دامنهی خالص؛ بدون اسکیما، هوک، اندپوینت یا کرون"
پول: "واحدهای صحیح خرد ریال؛ اعشار رد میشود نه گرد؛
بهسمت صفر گرد میشود تا کسب و پسگیری خنثی شوند"
دفتر: "فقط-افزودنی؛ موجودی = جمع رکوردها، هرگز یک ستون"
اصلاحها: "یک رکورد برگشت که میگوید کدام را خنثی میکند؛
هرگز ویرایش یا حذف"
سطلها: "نقدی / تبلیغاتی / بازپرداخت، نوعدار و قابل کوئری؛
جدا بر اساس مالیات، وضعیت حقوقی و انقضا؛
اعتبار فروشگاه اول خرج میشود"
امتیاز: "غیرقابلخرج ضرب میشود تا سفارش برگشتناپذیر شود؛
لغوشده و منقضی جدا از هم"
مبنای_کسب: "پس از تخفیف، پیش از مالیات، پیش از حمل،
با استثناهای نوعدار"
ممیزی: "خود رکوردها سابقهاند؛ بدون لاگ جدا"
اثبات: "بررسیکنندهی جهش نقصهای پولی شناختهشده را میکارد؛
تا الان چهار بار این کد را اصلاح کرده"
}ارزش حرفهای این پروژه
بررسیکنندهی جهش همان چیزی است که دوست دارم بازبین نگاهش کند. تست نوشتن حداقل انتظار است؛ اثبات اینکه تستها روی یک نقص واقعی قرمز میشوند، کاری است که تقریباً هیچکس نمیکند.
نساختن لایهی وردپرس تا وقتی دامنه اثبات نشده بود، انضباط میخواست و تصمیم درستی بود. مدلکردن صریح دستههای ارزش هم اولش گرانتر درمیآید و جلوی آن دسته باگی را میگیرد که بیعانهی یک قلم بیسروصدا روی قلم دیگری خرجشدنی میشود. باگ پولی که بعد از راهاندازی پیدا شود، دیگر باگ نیست؛ بدهی است.