در بخش قبل، کیفیت کد را با یک تغییر در سامانه سفارش سنجیدیم: لغو سفارش و درخواست بازپرداخت. همان مثال یک ابهام مهم را آشکار کرد. وقتی میگوییم «پرداخت انجام شد» یا «سفارش بسته شد»، دقیقاً کدام اتفاق را توصیف میکنیم؟ اگر اعضای تیم معنای یکسانی از این واژهها نداشته باشند، نامهای مرتب هم جلوی خطا را نمیگیرند.
این بخش درباره پیدا کردن نام از روی فهم دامنه است. هدف، حفظکردن قواعد ظاهری نامگذاری نیست؛ میخواهیم زبان گفتگو، مدل داده و رفتار API تا جای ممکن یک معنا داشته باشند. آشنایی ابتدایی با کد کافی است. خروجی، واژهنامه کوچک سفارش و پرداخت و چند قاعده برای بازبینی نامهاست.
یک واژه مبهم چطور به شرط اشتباه میرسد؟
فرض کنید فیلدی با نام is_done داریم. در یک صفحه، یعنی سفارش بستهبندی شده؛ در گزارش مالی، یعنی پرداخت تأیید شده؛ در اعلان، یعنی فرایند کار تمام شده است. اسم کوتاه است، ولی هر مصرفکننده مجبور میشود معنای خودش را به آن بدهد.
مشکل را با تبدیل نام به is_order_process_done حل نمیکنیم، چون هنوز «تمامشدن» تعریف نشده است. باید رویداد و تصمیم پشت آن را روشن کنیم. شاید به چند وضعیت مستقل نیاز داریم: پرداخت تأیید شده، آماده ارسال و تحویل شده. طول نام بهتنهایی مسئله را حل نمیکند؛ فهم تفاوت اتفاقها ضروری است.
از نمونههای واقعی گفتگو شروع کنیم
در جلسه با تیم عملیات بپرسید آخرین سفارش ناموفق چه وضعی داشت. از تیم مالی بخواهید تفاوت بازگشت وجه و ثبت درخواست آن را توضیح دهد. اگر واژهای در دو واحد معنی متفاوت دارد، اختلاف را ثبت کنید؛ قرار نیست با اجبار همه را به یک کلمه برسانیم.
برای نمونه، «تسویه» ممکن است در گفتگوی فروش با تأیید پرداخت مشتری یکی گرفته شود، ولی در حسابداری به انتقال وجه به پذیرنده اشاره کند. اگر این دو مفهوم در محصول لازماند، باید نام و مرز جدا داشته باشند. واژهنامه مشترک باید اختلاف را روشن کند، نه اینکه آن را زیر یک اسم عمومی پنهان کند.
سفارش، پرداخت و تلاش پرداخت را جدا کنیم
یک سفارش میتواند چند تلاش پرداخت داشته باشد: تلاش اول ناموفق میشود و کاربر دوباره اقدام میکند. اگر فقط یک فیلد وضعیت پرداخت روی سفارش داشته باشیم، ممکن است تاریخچه و معنای نتیجهها مبهم شود. نامها باید نشان دهند درباره کدام سطح صحبت میکنیم.
| مفهوم | تعریف در مثال | نام پیشنهادی کد |
|---|---|---|
| سفارش | درخواست خرید با اقلام و چرخه عمر مشخص | Order |
| تلاش پرداخت | یک تلاش مستقل برای پرداخت سفارش | PaymentAttempt |
| درخواست بازپرداخت | تقاضای ثبتشده برای بازگرداندن مبلغ | RefundRequest |
| نتیجه بازپرداخت | وضعیت معتبر اجرای بازگشت وجه | RefundResult |
این نامها نسخه نهایی همه محصولات نیستند. ارزش جدول در تعریف همراه اسم است. اگر مدل واقعی شما مسئولیت متفاوتی دارد، نام هم باید متناسب با آن انتخاب شود. کپیکردن نام بدون فهم معنا فقط ابهام را از پروژه دیگری منتقل میکند.
فعلها باید اثر را نشان دهند
تابعی با نام process ممکن است هر کاری انجام دهد. آیا فقط ورودی را بررسی میکند یا درگاه را صدا میزند و ایمیل هم میفرستد؟ خواننده نباید برای تشخیص یک اثر مهم، تمام بدنه را دنبال کند. نامهایی مثل request_refund و confirm_refund میتوانند دو مرحله متفاوت را روشن کنند، اگر واقعاً همان رفتار را داشته باشند.
بهخصوص میان «درخواستکردن»، «زمانبندیکردن» و «انجامدادن» فرق بگذارید. تابعی که کار را به صف میفرستد نباید طوری نامگذاری شود که انجام قطعی را القا کند. این تفاوت در API هم مهم است: پاسخ ثبت درخواست، الزاماً تأیید اجرای اثر بیرونی نیست.
واحد و مبنا را در مبلغ و زمان پنهان نکنیم
فیلد amount بدون مشخصبودن واحد و معنای مبلغ خطرناک است. مبلغ کل سفارش است یا مبلغ قابل پرداخت پس از تخفیف؟ واحد پول چیست؟ آیا هزینه ارسال در آن هست؟ پاسخ باید در قرارداد داده روشن باشد؛ بعضی تفاوتها را میتوان در نام و بعضی را در نوع و مستندات ثبت کرد.
زمان هم همین مسئله را دارد. date ممکن است زمان ایجاد، پرداخت یا ارسال باشد. نام paid_at تنها وقتی درست است که دقیقاً زمان معتبر پرداخت را نشان دهد. منطقه زمانی و نحوه نمایش باید جداگانه در قرارداد تعریف شوند؛ تغییر نام، خودبهخود تبدیل زمانی را درست نمیکند.
نام وضعیت را از رفتار قابل انجام جدا نکنیم
اگر وضعیتی به نام «تأییدشده» داریم، باید بپرسیم تأیید چه چیزی و توسط چه کسی؟ ممکن است پذیرش سفارش، تأیید درگاه و تأیید انبار سه اتفاق مستقل باشند. وضعیت عمومی CONFIRMED در چنین مدلی بیش از حد مبهم است.
برای هر وضعیت، یک تعریف و چند عملیات مجاز بنویسید. سفارش آماده ارسال با سفارش تحویلشده تفاوت دارد، چون قواعد لغو و بازپرداختشان ممکن است فرق کند. در بخش ماشین حالت، گذارها را دقیقتر مدل میکنیم؛ اینجا هدف این است که نام وضعیت به تصمیم دامنه وصل باشد.
نامگذاری خوب گاهی اختلاف مدل را آشکار میکند
اگر برای پیدا کردن نام یک تابع مدام به «و» نیاز داریم، شاید تابع چند مسئولیت دارد. «ثبت سفارش و کمکردن موجودی و ارسال پیام» میتواند یک مسیر کاربردی هماهنگ باشد، اما باید روشن باشد آیا نامش هدف کاربر را میگوید یا جزئیات پراکنده را فهرست میکند.
در مقابل، شکستن هر فعل به یک کلاس هم الزاماً بهتر نیست. نامهای خوب باید جریان را قابل دنبالکردن کنند. اگر خواننده برای فهم یک عملیات ساده میان تعداد زیادی فایل با نامهای تقریباً یکسان جابهجا میشود، شاید به جای دقیقترکردن مدل فقط واژههای بیشتری ساختهایم.
زبان مشترک یعنی تعریف قابل گفتگو
در طراحی دامنهمحور، زبان مشترک کمک میکند توسعهدهنده و آشنا به کسبوکار درباره مفهوم یکسانی صحبت کنند. این زبان قرار نیست فقط در سند بماند؛ باید در نام مدل، آزمون، پیام API و گفتگو قابل تشخیص باشد. نوشته مارتین فاولر درباره Ubiquitous Language مرجع کوتاهی برای این ایده است.
واژهنامه را کوچک نگه دارید. برای هر اصطلاح، تعریف، مثال و یک موردی که با آن اشتباه میشود بنویسید. مثلاً زیر «بازپرداخت انجامشده» اضافه کنید که «ثبت درخواست بازپرداخت» هنوز این وضعیت نیست. همین توضیح کوتاه میتواند از خطای گزارش و پیام مشتری جلوگیری کند.
تغییر نام هم میتواند سازگاری بخواهد
تغییر یک نام داخلی با تغییر فیلد عمومی API یکسان نیست. اگر مصرفکنندهای به نام قبلی وابسته است، حذف ناگهانی میتواند آن را خراب کند. ابتدا محدوده استفاده را پیدا کنید و برای قرارداد عمومی، مسیر مهاجرت مناسب طراحی کنید.
در دیتابیس هم بهتر است تفاوت تغییر نام فیزیکی و تغییر معنای داده روشن باشد. اگر معنای فیلد عوض شده، صرفاً rename کردن ممکن است داده قدیمی را با تفسیر تازه ناسازگار کند. این تغییر باید با قواعد تبدیل و بررسی داده همراه باشد؛ نام زیباتر بهتنهایی مهاجرت صحیح نیست.
بازبینی نام را به مثال وصل کنیم
هنگام مرور کد، از نویسنده بخواهید یک سفارش با دو تلاش پرداخت و یک درخواست بازپرداخت را با نامهای مدل روایت کند. اگر برای توضیح مجبور است مرتب بگوید «این فیلد اسمش این است ولی منظورش آن است»، یک نشانه قابل بررسی داریم.
برای تمرین، پنج نام مبهم از پروژه خود انتخاب کنید و برای هرکدام سه سؤال بنویسید: چه چیزی را نشان میدهد، چه چیزی را نشان نمیدهد و کدام رفتار به آن وابسته است؟ بخش بعد، همین واژهها و تعریفها را به مثالهای قابل آزمون و سناریوهای پذیرش تبدیل میکند.
برای مطالعه بیشتر
Ubiquitous Language، نوشته مارتین فاولر؛ برای نقش زبان دقیق و مشترک در مدل دامنه. جدولها و سناریوهای سفارش این مقاله، مثالهای تألیفی آموزشیاند.
