Skip to main content

قواعد کدنویسی (Coding Conventions)

این سند مجموعه‌ای از استانداردهای کدنویسی در پروژه‌های مبتنی بر Next.js را تعریف می‌کند.

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

هدف از این استانداردها:

  • افزایش خوانایی و درک‌پذیری کد
  • کاهش پیچیدگی‌های غیرضروری
  • ایجاد یک ساختار یکپارچه در کل پروژه
  • ساده‌تر شدن فرآیند نگهداری و توسعه
  • بهبود مقیاس‌پذیری (Scalability) در بلندمدت

اصول عمومی کدنویسی

اصول کدنویسی پایه‌ای‌ترین عامل در کیفیت نهایی یک پروژه هستند. در این پروژه، دو اصل کلیدی همواره باید رعایت شوند: یکپارچگی و خوانایی.

یکپارچگی در سبک کدنویسی

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

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

مثال: اگر در پروژه استفاده از camelCase برای متغیرها و توابع انتخاب شده است، نباید در بخش‌های دیگر از سبک‌های متفاوت مانند snake_case استفاده شود.

اولویت با خوانایی کد

کد باید در درجهٔ اول برای انسان‌ها قابل فهم باشد، نه صرفاً برای اجرا شدن.

برای افزایش خوانایی، رعایت موارد زیر ضروری است:

  • کد باید واضح، ساده و بدون ابهام نوشته شود
  • هر تابع باید فقط یک مسئولیت مشخص داشته باشد (Single Responsibility)
  • از نوشتن منطق‌های پیچیده در یک بلاک واحد باید اجتناب شود
  • ساختار فایل‌ها باید منطقی، قابل پیش‌بینی و قابل دنبال کردن باشد
  • منطق‌های پیچیده باید به بخش‌های کوچک‌تر و قابل تست تقسیم شوند

کدنویسی خوانا باعث می‌شود توسعه‌دهندگان بتوانند سریع‌تر کد را درک کنند، خطاها راحت‌تر پیدا شوند و همکاری تیمی به شکل مؤثرتری انجام شود.


قراردادهای کامپوننت در React

رعایت یک الگوی ثابت برای ساخت و سازمان‌دهی کامپوننت‌ها باعث می‌شود پروژه‌های React خواناتر، قابل‌نگهداری‌تر و قابل‌توسعه‌تر باشند. در ادامه چند قانون رایج برای ساختار کامپوننت‌ها آورده شده است.

یک کامپوننت در هر فایل

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

ساختار استاندارد کامپوننت

برای هر کامپوننت بهتر است یک پوشهٔ جداگانه ایجاد شود و فایل‌های مرتبط با آن داخل همان پوشه قرار بگیرند. این ساختار باعث می‌شود کدهای مربوط به هر کامپوننت در یک مکان مشخص نگهداری شوند.

ساختار معمول یک کامپوننت به شکل زیر است
Button/
├── Component.tsx
├── index.ts
├── types.ts

در این ساختار:

  • فایل Component.tsx شامل پیاده‌سازی اصلی کامپوننت است.
  • فایل types.ts برای تعریف TypeScript type یا interfaceهای مربوط به آن کامپوننت استفاده می‌شود.
  • فایل index.ts برای export کردن کامپوننت استفاده می‌شود تا import کردن آن در بخش‌های دیگر پروژه ساده‌تر باشد.

اجتناب از کامپوننت‌های بیش از حد بزرگ

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

در شرایط زیر بهتر است کامپوننت را به بخش‌های کوچک‌تر تقسیم کنید:

  • زمانی که کامپوننت بیش از حد طولانی شده است.
  • زمانی که چند مسئولیت متفاوت را انجام می‌دهد.
  • زمانی که خوانایی کد کاهش پیدا کرده است.
  • تقسیم کامپوننت‌ها به بخش‌های کوچک‌تر باعث می‌شود کد قابل‌استفادهٔ مجدد، خواناتر و قابل‌تست‌تر شود.

قراردادهای توابع

رعایت الگوهای ثابت در نوشتن توابع باعث می‌شود کد تمیزتر، قابل‌خواندن‌تر و قابل‌نگهداری‌تر باشد. در این بخش چند قاعدهٔ مهم برای تعریف و ساختار توابع توضیح داده می‌شود.

ترجیح استفاده از Arrow Function

در کدهای مدرن جاوااسکریپت و تایپ‌اسکریپت، بهتر است تا جای ممکن از Arrow Function استفاده شود و از function به‌صورت declaration معمولی استفاده نشود. استفاده از Arrow Function‌ها باعث یکنواختی در سبک کدنویسی می‌شود و رفتار آن‌ها در مورد this نیز قابل‌پیش‌بینی‌تر است.

نمونه نامناسب
function calculateTotal() {}
نمونه مناسب
const calculateTotal = () => {}

در این روش، تابع به‌صورت یک مقدار ثابت تعریف می‌شود و امکان مدیریت بهتر و هماهنگ‌تری در سطح کد فراهم می‌شود.

توابع کوچک و تک‌مسئولیتی

هر تابع باید تا حد امکان:

  • ساده
  • کوتاه
  • و دارای یک مسئولیت مشخص (Single Responsibility)

باشد.

یعنی تابع باید فقط یک کار مشخص را انجام دهد و به‌جای انجام چند کار مختلف، برای هر وظیفهٔ جداگانه، تابع مخصوص خودش نوشته شود. این کار باعث می‌شود:

  • تست‌کردن توابع ساده‌تر شود،
  • رفع باگ‌ها سریع‌تر انجام شود،
  • و خواندن و فهمیدن منطق کد آسان‌تر باشد.

اجتناب از تو در تو شدن زیاد شرط‌ها (Deep Nesting)

بهتر است از تو در تو شدن زیاد شرط‌ها (deep nesting) جلوگیری شود؛ چون این کار خوانایی کد را به‌شدت کاهش می‌دهد.

نمونه نامناسب
if (a) {
if (b) {
if (c) {
// Logic Block
}
}
}

به‌جای این کار، بهتر است از early return استفاده شود؛ یعنی در همان ابتدا شرایط نامعتبر را بررسی کرده و زود از تابع خارج شوید:

نمونه مناسب
if (!a) return
if (!b) return
if (!c) return
// Logic Block

با این روش:

  • سطح تورفتگی (indentation) کمتر می‌شود،
  • ساختار کد خطی‌تر و قابل‌خواندن‌تر است،
  • و منطق تابع به‌راحتی دنبال می‌شود.

رندر شرطی (Conditional Rendering)

رندر کردن شرطی در React به معنی نمایش دادن یا ندادن بخش‌هایی از رابط کاربری بر اساس شرایط خاص است. استفادهٔ درست از این قابلیت باعث می‌شود رابط کاربری پویا و کاربرپسندتری داشته باشیم.

ترجیح استفاده از Early Return

یکی از روش‌های مؤثر برای مدیریت رندر شرطی، استفاده از Early Returns است. این روش به این معنی است که قبل از رسیدن به بخش اصلی رندر، شرایط اولیه را بررسی کرده و در صورت نیاز، زودتر از تابع خارج شوید.

مثال
if (isLoading) return <Loader />
if (!user) return null

return <Dashboard />

در این مثال:

  • ابتدا بررسی می‌شود که آیا وضعیت بارگذاری (isLoading) برقرار است یا خیر. اگر بله، کامپوننت Loader نمایش داده می‌شود و تابع پایان می‌یابد.
  • اگر در حال بارگذاری نباشیم، بررسی می‌شود که آیا کاربر (user) وجود دارد یا خیر. اگر کاربر وجود نداشته باشد، null برگردانده می‌شود (یعنی چیزی نمایش داده نمی‌شود).
  • اگر هیچ‌کدام از این شرایط برقرار نباشد (یعنی هم در حال بارگذاری نیستیم و هم کاربر وجود دارد)، کامپوننت Dashboard نمایش داده می‌شود.

این روش باعث می‌شود کد خواناتر و خطی‌تر باشد و از تو در تو شدن زیاد JSX جلوگیری می‌کند.

اجتناب از شرط‌های پیچیده در JSX

از به‌کارگیری شرط‌های پیچیده و زنجیره‌ای در JSX خودداری کنید؛ زیرا خوانایی کد را کاهش می‌دهد.

نمونه نامناسب
{a && b && c && d && ...}

این نوع شرط‌ها، به‌خصوص زمانی که تعدادشان زیاد باشد، فهمیدن اینکه چه زمانی چه چیزی نمایش داده می‌شود را دشوار می‌کند.

بهتر است منطق شرط را به یک متغیر جداگانه منتقل کرده و سپس از آن در JSX استفاده کنید.

نمونه مناسب
const shouldShowBanner = ...

return shouldShowBanner ? <Banner /> : null

در این روش:

  • ابتدا یک متغیر بولی (shouldShowBanner) تعریف می‌شود که نتیجهٔ شرط مورد نظر را در خود نگه می‌دارد.
  • سپس با استفاده از عملگر Ternary، یا کامپوننت Banner نمایش داده می‌شود و یا null (یعنی هیچی نمایش داده نمی‌شود).

این کار باعث می‌شود JSX تمیزتر بماند و منطق شرطی در بخش منطق کامپوننت (یا خارج از JSX) قرار گیرد.


قراردادهای استایل‌دهی

رعایت استانداردهای طراحی در استایل‌دهی، باعث حفظ نظم بصری پروژه و افزایش سرعت توسعه می‌شود. در ادامه، اصولی برای مدیریت بهتر استایل‌ها آورده شده است.

ترجیح استفاده از کلاس‌های معناگرا (Semantic Classes)

به‌جای استفاده مستقیم و پراکنده از کلاس‌های CSS (مانند کلاس‌های Utility-first در Tailwind CSS) برای عناصر تکراری، بهتر است از معناگرایی (Semantic) استفاده کنید. این کار به شما کمک می‌کند تا به‌جای تکرار کدهای استایل، از مفاهیم انتزاعی استفاده کنید.

نمونهٔ غیرمعمول (تکرار کلاس‌های Utility)
'bg-blue-500 text-white px-4'
نمونهٔ پیشنهادی (استفاده از انتزاع یا Abstraction)
'btn-primary'
'card-container'

استفاده از رویکردهایی مثل token-based styles به شما اجازه می‌دهد تا به‌جای مقادیر خام CSS، از متغیرهای معنادار استفاده کنید که باعث می‌شود تغییر استایل‌ها در آینده بسیار آسان‌تر شود.

قابل‌پیش‌بینی نگه داشتن سیستم استایل

برای داشتن یک رابط کاربری منسجم، استایل‌دهی باید قابل‌پیش‌بینی باشد. برای این هدف، حتماً موارد زیر را در سطح پروژه تعریف و رعایت کنید:

  • سیستم فاصله‌گذاری (Spacing system): استفاده از مقادیر ثابت برای margin و padding.
  • توکن‌های رنگی (Color tokens): تعریف پالت رنگی مشخص برای حالت‌های مختلف.
  • مقیاس تایپوگرافی (Typography scale): استفاده از سایزهای استاندارد برای فونت‌ها.
  • مقیاس انحنا (Radius scale): تعریف مقادیر مشخص برای گوشه‌های گرد عناصر.

رعایت این مقیاس‌ها باعث می‌شود طراحی رابط کاربری شما یکپارچه و حرفه‌ای به نظر برسد.

نکته مهم

این الگوی abstraction و semantic class composition بهتر است فقط برای کامپوننت‌های اصلی و پایه (Core Components) استفاده شود. این کامپوننت‌ها معمولاً بخش مهمی از Design System پروژه هستند و داشتن ساختار ثابت و قابل‌پیش‌بینی در آن‌ها اهمیت زیادی دارد.

برای سایر بخش‌های پروژه، مخصوصاً بخش‌هایی که ساده‌تر هستند یا فقط یک‌بار استفاده می‌شوند، استفاده از inline Tailwind CSS کاملاً قابل‌قبول است و حتی در بسیاری از مواقع باعث سریع‌تر شدن توسعه و کاهش پیچیدگی می‌شود.

همچنین در بعضی بخش‌ها، تیم SEO نیاز دارد برخی عناصر صفحه را از طریق classNameهای مشخص target کند. به همین دلیل، استفاده از semantic class nameها در برخی کامپوننت‌ها صرفاً یک انتخاب فنی نیست و می‌تواند یک نیاز سفارشی در پروژه نیز باشد.


قوانین کامنت‌گذاری

استفاده از کامنت در کد باید هدفمند باشد. کامنت‌ها نباید جایگزین کدنویسی خوب شوند، بلکه باید در جاهایی استفاده شوند که فهمیدن دلیل یک تصمیم از روی خود کد ممکن نیست.

کد باید تا حد امکان خودگو باشد

در حالت ایده‌آل، کد باید تا حد زیادی خودش قابل‌فهم باشد. این یعنی با استفاده از نام‌گذاری مناسب برای متغیرها، توابع و ساختار کد، خواننده بتواند بدون نیاز به کامنت متوجه عملکرد آن شود.

به همین دلیل، در بسیاری از مواقع بهتر است به‌جای اضافه کردن کامنت، نام‌های واضح‌تر و توصیفی‌تر برای بخش‌های مختلف کد انتخاب شود.

توضیح «چرا» به جای «چه کاری» انجام می‌شود

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

نمونه نامناسب
// increment i
i++

در این مثال، کامنت هیچ اطلاعات جدیدی اضافه نمی‌کند.

در عوض، کامنت‌ها باید دلیل انجام یک کار (WHY) را توضیح دهند؛ یعنی چرا چنین تصمیمی در کد گرفته شده است.

نمونه مناسب
// retry once because API occasionally returns stale cache

در اینجا کامنت توضیح می‌دهد که چرا یک رفتار خاص در کد وجود دارد.

افزودن کامنت برای منطق پیچیدهٔ کسب‌وکار

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

این کامنت باید توضیح دهد که:

  • چرا این منطق در سیستم وجود دارد
  • چه محدودیت یا قانون تجاری باعث این پیاده‌سازی شده است

این کار به توسعه‌دهندگانی که بعداً روی پروژه کار می‌کنند کمک می‌کند سریع‌تر منطق پشت کد را درک کنند و از ایجاد تغییرات اشتباه جلوگیری شود.


قوانین Barrel Export

به‌صورت کلی از barrel export استفاده نمی‌کنیم.

بد:

export * from './Button'

موارد مجاز استفاده

استفاده از Barrel Export (مثل فایل‌های index.ts یا index.css برای جمع‌کردن exportها) فقط در شرایط مشخص مجاز است تا از پیچیدگی و importهای غیرشفاف جلوگیری شود.

Barrel export فقط زمانی قابل‌قبول است که یکی از این حالت‌ها برقرار باشد:

  • زمانی که تمام exportها صرفاً داخل همان ماژول (module) استفاده می‌شوند و قرار نیست به شکل گسترده در کل پروژه پخش شوند.
  • زمانی که برای یک aggregation نهایی لازم است؛ یعنی چند فایل مرتبط صرفاً برای ساختن یک نقطهٔ ورود (entry) نهایی کنار هم جمع می‌شوند.
مثال
styles/
├── button.css
├── form.css
├── index.css

در این سناریو، فایل index.css نقش تجمیع‌کننده را دارد و در نهایت فقط یک نقطهٔ ورود اصلی مثل globals.css باید همان entry نهایی را import کند، تا ورودی استایل‌ها در پروژه شفاف و قابل‌مدیریت باقی بماند.

globals.css

فقط یک entry نهایی را import کند.


قراردادهای مخصوص Next.js

در پروژه‌هایی که از Next.js (App Router) استفاده می‌کنند، رعایت چند الگوی مشخص باعث می‌شود ساختار پروژه قابل‌فهم‌تر و عملکرد آن بهینه‌تر باشد.

پوشه‌های Route با نام‌گذاری kebab-case

تمام پوشه‌هایی که به‌عنوان route استفاده می‌شوند باید با سبک kebab-case نام‌گذاری شوند. در این سبک، کلمات با خط تیره (-) از هم جدا می‌شوند.

مثال
app/user-profile/
app/blog-post/
app/payment-history/

استفاده از kebab-case باعث می‌شود مسیرهای URL خواناتر باشند و ساختار routing پروژه یکدست باقی بماند.

Server Componentها به‌صورت پیش‌فرض

در App Router در Next.js، کامپوننت‌ها به‌صورت پیش‌فرض Server Component هستند. بنابراین تا زمانی که واقعاً نیاز نباشد، نباید از دستور use client استفاده شود.

Client Component فقط زمانی باید استفاده شود که به قابلیت‌هایی نیاز دارید که فقط در مرورگر قابل اجرا هستند، مانند:

  • مدیریت state
  • استفاده از React effects
  • دسترسی به Browser APIs
  • پیاده‌سازی interactivity (تعامل کاربر با UI)

با نگه داشتن بیشتر کامپوننت‌ها در سمت سرور، می‌توان عملکرد بهتر و حجم JavaScript کمتری در سمت کلاینت داشت.


جمع بندی

هدف از تعریف Conventionها در یک پروژه این نیست که توسعه را سخت‌تر یا محدودتر کنند. در واقع، این قواعد باید به تیم کمک کنند تا کدها خواناتر، قابل‌پیش‌بینی‌تر و قابل‌نگهداری‌تر شوند.

Conventionها در نهایت برای رسیدن به چند هدف اصلی تعریف می‌شوند:

  • افزایش readability (خوانایی کد)
  • ایجاد predictability (قابل‌پیش‌بینی بودن ساختار پروژه)
  • بهبود maintainability (نگهداری آسان‌تر کد در طول زمان)
  • فراهم کردن scalability (امکان رشد و توسعه پروژه)

یک پروژهٔ خوب صرفاً پروژه‌ای نیست که قوانین زیادی داشته باشد. تعداد زیاد قوانین لزوماً به معنی کیفیت بالاتر نیست.

پروژهٔ خوب پروژه‌ای است که قوانین آن:

  • واضح باشند
  • ساده باشند
  • قابل‌پیش‌بینی باشند
  • و در کل پروژه به‌صورت consistent رعایت شوند

وقتی Conventionها به شکل درست تعریف و اجرا شوند، توسعه‌دهندگان می‌توانند بدون اتلاف وقت روی ساختار کد، تمرکز اصلی خود را روی حل مسئله و توسعهٔ قابلیت‌های جدید بگذارند.