توسعه فرانت‌اند

Clean Code در JavaScript: اصول کد تمیز

نام، واحد، مرز اعتبارسنجی، اثر بیرونی و خطا با نمونه کامل Node برای محاسبه آموزشی؛ کد کوتاه‌تر لزوماً روشن‌تر نیست.

1404/11/24 1 دقیقه مطالعه 1,177 بازدید

کد تمیز کدی نیست که همیشه کمترین خط را داشته باشد. باید بتوانید رفتار، ورودی، خروجی و محدودیت آن را بدون حدس زیاد بفهمید و تغییر دهید. نام دقیق، مرز مسئولیت و قرارداد خطا معمولاً از ترفندهای کوتاه‌نویسی مهم‌ترند. یک عبارت پیچیده یک‌خطی می‌تواند از چند مرحله ساده دشوارتر باشد. معیار اصلی، خوانایی برای کسی است که با مسئله واقعی و زمینه پروژه کار می‌کند، نه زیبایی ظاهری جدا از رفتار.

اصول Clean Code را به قانون‌های مطلق تبدیل نکنید. طول تابع، تعداد آرگومان و میزان انتزاع به مسئله وابسته‌اند. حذف تکرار هم اگر دو مفهوم متفاوت را به یک تابع عمومی اجباری تبدیل کند، نگهداری را سخت‌تر می‌کند. هدف این مقاله ساختن چند سؤال عملی برای نوشتن و بازخوانی JavaScript است. نمونه کوچک آموزشی داریم، اما آن را معماری آماده یک سامانه فروش یا توصیه همگانی برای همه پروژه‌ها معرفی نمی‌کنیم.

نام باید نقش و دامنه را نشان دهد

نام data یا handle در محدوده بسیار کوچک شاید کافی باشد، ولی برای منطق مهم اطلاعات کمی می‌دهد. نامی مانند calculateOrderTotal نشان می‌دهد تابع محاسبه می‌کند و موضوعش چیست. اگر تابع علاوه بر محاسبه درخواست شبکه می‌فرستد، نام و مسئولیت ناسازگارند. متغیر boolean بهتر است سؤال روشن ایجاد کند؛ مثلاً isReady. اسم‌ها را با زبان دامنه هماهنگ کنید و برای مفهوم یکسان بی‌دلیل چند واژه مختلف نسازید.

واحد مقدار را هم در نام یا قرارداد روشن کنید. قیمت به ریال است یا تومان؟ زمان بر حسب میلی‌ثانیه است یا ثانیه؟ تفاوت عددها ممکن است از همین ابهام بیاید. نام طولانی که فقط جمله را تکرار می‌کند لزوماً بهتر نیست؛ اطلاعات تصمیم‌ساز را نگه دارید. API و اصطلاح رایج را به ترجمه نامأنوس تبدیل نکنید. خواننده باید بتواند آن را در کد و مستندات جست‌وجو کند.

تابع یک قرارداد قابل توضیح داشته باشد

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

مرز بیرونی را با دقت بیشتری بررسی کنید: JSON شبکه، ورودی فرم و فایل قابل اعتماد ذاتی نیستند. تایپ اعلام‌شده به تنهایی مقدار زمان اجرا را معتبر نمی‌کند. راهنمای Zod برای قرارداد Schema مفید است. در تابع داخلی که ورودی قبلاً معتبر شده، تکرار همه بررسی‌ها شاید لازم نباشد؛ مرز اعتماد را مشخص کنید تا اعتبارسنجی پراکنده و متناقض نشود.

نمونه کامل محاسبه آموزشی

کد زیر را با نام order-total.mjs ذخیره کنید و با Node نسخه ۲۲ به بالا، فرمان node order-total.mjs را اجرا کنید. هیچ package یا فایل دیگری لازم نیست. مبلغ‌ها عدد صحیح در واحد فرضی آموزشی‌اند و مثال برای حسابداری، مالیات یا پرداخت واقعی طراحی نشده است. تابع قیمت واحد و تعداد را بررسی می‌کند، ورودی را تغییر نمی‌دهد و مجموع را برمی‌گرداند. داده نمونه شامل دو ردیف است.

export function calculateOrderTotal(items) {
  if (!Array.isArray(items)) throw new TypeError('items باید آرایه باشد.');
  let total = 0;
  for (const item of items) {
    if (!item || !Number.isSafeInteger(item.unitPrice) || item.unitPrice < 0) {
      throw new TypeError('قیمت واحد باید عدد صحیح امن و نامنفی باشد.');
    }
    if (!Number.isSafeInteger(item.quantity) || item.quantity < 1) {
      throw new TypeError('تعداد باید عدد صحیح امن و مثبت باشد.');
    }
    const lineTotal = item.unitPrice * item.quantity;
    if (!Number.isSafeInteger(lineTotal) || !Number.isSafeInteger(total + lineTotal)) {
      throw new RangeError('مجموع از دامنه عدد صحیح امن خارج است.');
    }
    total += lineTotal;
  }
  return total;
}
console.log(calculateOrderTotal([
  { unitPrice: 100, quantity: 2 },
  { unitPrice: 70, quantity: 1 },
]));
try { calculateOrderTotal([{ unitPrice: 100, quantity: 0 }]); }
catch (error) { console.log(error.message); }

خروجی بخش اول برابر ۲۷۰ است: دو واحد از کالای صدواحدی و یک واحد از کالای هفتادواحدی. بخش دوم عمداً تعداد صفر دارد و پیام خطای قرارداد را نشان می‌دهد. این خطا به معنی اجرای تست خودکار نیست؛ بخشی از نمونه آموزشی است. نتیجه‌های گفته‌شده از همان داده و محاسبه می‌آیند و به فروش واقعی ارتباط ندارند. حد عدد صحیح امن نیز برای جلوگیری از نتیجه عددی نامعتبر بررسی شده است.

تغییر ورودی را آشکار نگه دارید

تابعی که آرایه ورودی را مرتب یا شیء آن را تغییر می‌دهد می‌تواند روی مصرف‌کننده دیگر اثر بگذارد. اگر Mutation لازم است، قراردادش روشن باشد. در نمونه فقط خواندن انجام می‌شود و هر ردیف حفظ می‌شود. ساختن نسخه سطحی نیز تضمین نمی‌کند همه اشیای تو در تو مستقل شده‌اند. قبل از انتخاب کپی یا تغییر مستقیم، رابطه مالکیت داده را بفهمید. انتخاب باید از نیاز بیاید، نه شعار همیشگی.

متدهایی مانند map آرایه جدید می‌سازند، اما اگر در Callback شیء قدیمی را تغییر دهید، مرز مستقل خودکار ایجاد نمی‌شود. مستندات map در MDN رفتار آن را توضیح می‌دهد. از متد براساس هدف استفاده کنید؛ ساختن آرایه‌ای که نتیجه‌اش دور ریخته می‌شود شاید انتخاب روشنی نباشد. حلقه ساده هم اگر نیت را بهتر نشان دهد می‌تواند مناسب باشد.

محاسبه را از اثر بیرونی جدا کنید

محاسبه مجموع با ارسال فاکتور یا ذخیره سفارش مسئولیت متفاوتی دارد. جداکردن این‌ها فهم خطا و استفاده مجدد را آسان‌تر می‌کند. تابع محاسبه بدون شبکه و زمان جاری قابل توضیح‌تر است. اما جداکردن هر خط به تابع تازه نیز لزوماً سود ندارد. مرز باید مفهوم داشته باشد. اگر برای فهم یک کار کوچک مجبورید میان ده فایل بپرید، انتزاع شاید بیش از نیاز شده باشد.

اثر بیرونی مانند درخواست HTTP باید خطای روشن و مسیر رسیدگی داشته باشد. catch خالی می‌تواند شکست را پنهان کند و برنامه را در وضعیت مبهم بگذارد. پیام کاربر را از داده فنی جدا کنید و اطلاعات خصوصی را بی‌دلیل Log نکنید. راهنمای Fetch و Axios برای مرز شبکه مفید است. کد تمیز به معنی نبود خطا نیست؛ خطا باید قابل فهم و قابل پیگیری باشد.

شرط‌ها و حالت‌های مرزی

شرط‌ها را با مفهوم دامنه بیان کنید. اگر یک عبارت چند بار تکرار شده و معنای مشخصی دارد، نام‌گذاری آن می‌تواند کمک کند. Early Return برای رد حالت نامعتبر گاهی مسیر اصلی را روشن می‌کند، اما چند خروجی نامرتبط بدون قرارداد می‌تواند گیج‌کننده باشد. حالت خالی، مقدار صفر، نبود فیلد و مقدار نامعتبر یکسان نیستند. آن‌ها را از روی نیاز واقعی تعریف کنید، نه فقط با رفتار truthy و falsy.

برای مثال، استفاده از value || default ممکن است صفر معتبر را هم جایگزین کند. انتخاب عملگر باید با معنای نبود مقدار هماهنگ باشد. داده‌های عددی، رشته خالی و null نیاز به تصمیم دارند. در نمونه، آرایه خالی مجموع صفر می‌دهد ولی تعداد صفر یک ردیف رد می‌شود؛ این یک قرارداد آموزشی مشخص است. اگر دامنه شما اجازه کالای رایگان یا تعداد متفاوت می‌دهد، باید قرارداد را آگاهانه تغییر دهید.

تکرار و انتزاع را با هم بسنجید

دو قطعه مشابه لزوماً یک مفهوم نیستند. اگر با هر تغییر یک مصرف‌کننده، شرط تازه‌ای به تابع عمومی اضافه می‌شود، شاید مرز اشتباه است. پیش از ساخت Helper، نقش و دلیل اشتراک را بنویسید. تکرار کوچک و روشن می‌تواند تا زمان معلوم‌شدن الگو قابل قبول باشد. برعکس، تکرار یک قاعده واقعی قیمت یا دسترسی در چند محل می‌تواند ناسازگاری ایجاد کند و نیاز به مرجع مشترک داشته باشد.

قانون‌های دامنه را از قالب نمایش جدا کنید. فرمت فارسی مبلغ نباید مقدار محاسبه را به رشته‌ای غیرقابل استفاده تبدیل کند. خروجی محاسبه و نمایش می‌توانند لایه جدا باشند. برای پروژه بزرگ، ساختار فایل باید امکان پیداکردن این مرز را بدهد. نام پوشه «utils» به تنهایی معماری نیست؛ اگر همه چیز در آن جمع شود، مسئولیت‌ها دوباره مبهم می‌شوند.

کامنت و ابزارهای تیمی

کامنت بهتر است چرایی غیرآشکار یا محدودیت مهم را توضیح دهد، نه اینکه هر خط را به زبان دیگر تکرار کند. توضیح قدیمی و غلط می‌تواند از نبود توضیح بدتر باشد. اگر نام و ساختار نیاز به شرح طولانی دارند، خود کد را هم بازبینی کنید. ESLint و Prettier برای قراردادهای قابل خودکارسازی مفیدند؛ راهنمای Lint و Format تفاوت آن‌ها را توضیح می‌دهد.

هیچ ابزاری تمام کیفیت طراحی را تشخیص نمی‌دهد. بازخوانی باید ورودی، نتیجه، نام، اثر بیرونی و حالت خطا را کنار هم ببیند. در تغییر واقعی، بررسی مناسب دامنه لازم است؛ این مقاله ادعای اجرای نمونه روی پروژه شما ندارد. یک اصلاح کوچک با هدف روشن از بازنویسی گسترده صرفاً برای سلیقه ظاهری قابل ارزیابی‌تر است. کد تمیز باید فهم و تغییر رفتار را آسان کند و هزینه نگهداری را بی‌دلیل بالا نبرد.

واکنش به این مقاله

اشتراک‌گذاری این مقاله:
دیدگاه‌ها 0
۰ / ۲۰۰۰

هنوز دیدگاهی ثبت نشده

اولین نفری باشید که نظر می‌دهد!