استانداردهای کدنویسی در سالیدیتی

در این مقاله قصد نداریم راهنمای رسمی Solidity Style Guide را تکرار کنیم، بلکه می‌خواهیم به نکات و خطاهای رایجی بپردازیم که توسعه دهندگان اغلب در بازبینی کد (Code Review) یا حسابرسی امنیتی (Audit) قراردادهای هوشمند مرتکب می‌شوند. برخی از نکاتی که در این‌جا مطرح می‌شود در راهنمای رسمی وجود ندارد، اما خطاهای رایج سبک نگارشی در میان توسعه دهندگان Solidity محسوب می‌شوند.

دو خط اول هر فایل در سالیدیتی

۱. اضافه کردن SPDX-License-Identifier

درست است که نبود این خط باعث خطای کامپایل نمی‌شود، اما هشدار (warning) تولید می‌کند. پس بهتر است این هشدار را از بین ببرید.

۲. مشخص کردن دقیق نسخه solidity مگر اینکه کتابخانه بنویسید

احتمالاً تا به حال دو نوع نحوه نگارش نسخه Solidity را دیده‌اید:

و
اما کدام‌یک را باید استفاده کرد و در چه شرایطی؟ اگر خودتان مسئول کامپایل و استقرار (Deploy) قرارداد هستید و می‌دانید دقیقاً از کدام نسخه کامپایلر استفاده می‌کنید، توصیه می‌شود نسخه را به‌صورت دقیق مشخص کنید. این کار باعث می‌شود کد شما قابل پیش‌بینی‌تر و بررسی آن ساده‌تر باشد.

اما در حالتی که در حال توسعه یک کتابخانه (Library) هستید — مانند آن‌چه پروژه های OpenZeppelin یا Solady انجام می‌دهند — نباید نسخه را قفل (fix) کنید. دلیل آن ساده است: شما نمی‌دانید توسعه دهنده ای که از کتابخانه تان استفاده می‌کند از چه نسخه ای از کامپایلر استفاده خواهد کرد.

وارد کردن (Import) فایل ها در Solidity

۳. نسخه کتابخانه را به‌صورت دقیق در دستور import مشخص کنید

به جای این که بنویسید:

بنویسید:
برای پیدا کردن آخرین نسخه پایدار (clean version)، کافی است در صفحه GitHub مربوط به کتابخانه، روی منوی کشویی “branch” در سمت چپ کلیک کرده و وارد بخش “Tags” شوید. سپس جدیدترین نسخه ای را انتخاب کنید که برچسب های آزمایشی (مثل rc به معنای Release Candidate) نداشته باشد.

اسکرین‌شات گیت‌هاب که نسخه را نشان می‌دهد

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

۴. از Named Import استفاده کنید، نه کل فضای نام (Namespace)

به جای این‌که به شکل زیر تمام محتوای فایل را وارد کنید:

بهتر است فقط اجزای مورد نیاز را وارد نمایید:
اگر فایلی که وارد می‌کنید شامل چندین قرارداد یا کتابخانه باشد، وارد کردن همه آن‌ها باعث آلودگی فضای نام (Namespace Pollution) می‌شود. این موضوع نه‌تنها خوانایی کد را کاهش می‌دهد، بلکه در صورت عدم حذف خودکار توسط بهینه ساز کامپایلر، ممکن است منجر به کدهای مرده (Dead Code) شود. بهینه سازی کامپایلر هم همیشه قابل اتکا نیست، پس بهتر است از همان ابتدا فقط موارد مورد نیاز را وارد کنید.

۵. import های بدون استفاده را حذف کنید

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

سطح قرارداد (Contract Level) در سالیدیتی

۶. استفاده از مستندسازی سطح قرارداد با NatSpec

هدف از استفاده از NatSpec (Natural Specification) ایجاد مستنداتی خوانا برای انسان، به‌صورت درون کدی (inline) است. این نوع مستندسازی، اطلاعات مفیدی را برای کاربران نهایی، توسعه دهندگان و ابزارهای تحلیل کد فراهم می‌کند.

در زیر یک نمونه مستند سازی NatSpec برای سطح قرارداد آورده شده است:

در اینجا:

  • @title: عنوان قرارداد را مشخص می‌کند.

  • @author: نام یا سازمان نویسنده را درج می‌کند.

  • @notice: توضیحاتی برای مخاطب غیر فنی فراهم می‌کند.

  • @dev: نکاتی تخصصی برای توسعه دهندگان ارائه می‌دهد.

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

۷. ساختار دهی منظم به بدنه قرارداد طبق راهنمای سبک نگارش

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

  1. سطح دسترسی (Externality): توابع ابتدا بر اساس سطح دسترسی شان دسته بندی می‌شوند:

    • توابع receive و fallback (در صورت وجود)

    • توابع external

    • توابع public

    • توابع internal

    • توابع private

  2. میزان تغییر وضعیت (State-Changingness): در هر دسته، توابع باید به ترتیب زیر مرتب شوند:

    • توابع payable

    • توابع بدون payable

    • توابع view

    • توابع pure

نمونه ای از چینش صحیح ساختار قرارداد:

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

ثابت ها (Constants) در سالیدیتی

۸. جایگزینی اعداد جادویی (Magic Numbers) با مقادیر ثابت

اگر عددی مانند 100 را به‌تنهایی در کد ببینید، فوراً مشخص نیست منظور چیست؛ آیا منظور ۱۰۰ درصد است؟ ۱۰۰ واحد پایه (basis points)؟ یا چیز دیگر؟

برای خوانایی و نگهداری بهتر کد، اعداد باید به‌صورت ثابت (constant) در بالای قرارداد تعریف شوند. برای مثال:

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

۹. استفاده از کلمات کلیدی زمان و اتر در سالیدیتی

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

مثال نادرست:

مثال صحیح:
استفاده از 1 days، 1 weeks، 0.5 ether و مانند آن باعث افزایش خوانایی و کاهش احتمال خطاهای محاسباتی می‌شود.

۱۰. استفاده از زیرخط (_) برای خواناتر کردن اعداد بزرگ

اعداد بزرگ را می‌توان با استفاده از علامت زیرخط (underscore) قابل‌فهم‌تر کرد:

مثلا بجای اینکه بنویسید:

بنویسید:

این روش فقط ظاهر عدد را خواناتر می‌کند و تأثیری بر عملکرد برنامه ندارد.

توابع (Functions) در سالیدیتی

۱۱. حذف کلید واژه virtual از توابعی که قرار نیست بازنویسی شوند

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

این کار باعث کاهش ابهام و بهبود امنیت قرارداد می‌شود.

۱۲. رعایت ترتیب صحیح در تعریف مشخصه های تابع

ترتیب مشخصه ها در امضای توابع باید طبق الگوی زیر باشد:

دسترسی (visibility) → قابلیت تغییر وضعیت (mutability) → virtual → override → محدودکننده های سفارشی (modifiers)

مثال صحیح:

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

۱۳. استفاده صحیح از Natspec در سالیدیتی

Natspec که گاهی به‌اشتباه به آن «سبک کامنت‌گذاری در Solidity» گفته می‌شود، در واقع یک استاندارد مستندسازی رسمی برای قراردادهای هوشمند در Solidity است که مخفف Ethereum Natural Language Specification Format می‌باشد.

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

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

ساختار Natspec در توابع شبیه به کامنت گذاری در سطح قرارداد است، با این تفاوت که در اینجا پارامترها (@param) و مقدار بازگشتی (@returns) نیز مشخص می‌شوند. همچنین می‌توان توضیحاتی درباره رفتار فنی تابع (با @dev) ارائه کرد.

مثال استاندارد:

استفاده از @param برای شرح هر پارامتر، و @return برای مشخص کردن خروجی تابع، به خوانایی بیشتر کد کمک می‌کند. حتی اگر نام متغیر ها خیلی خلاصه باشد، توضیحات Natspec می‌توانند آن‌ها را معنا‌ دار کنند.

ارث بری از مستند سازی (natspec) در توابع به ارث‌ رسیده

اگر تابعی از قرارداد پایه به ارث برده‌اید و مستندسازی در آن وجود دارد، می‌توانید با @inheritdoc به سادگی مستندات را از قرارداد پایه به ارث ببرید:

چه چیزهایی را در @dev توضیح دهیم؟

در قسمت @dev معمولاً موارد فنی‌تر مثل تغییرات در وضعیت قرارداد، ارسال اتر، صدور رویداد، فراخوانی های حساس یا عملیات مخرب (مثل selfdestruct) ذکر می‌شود.

مستند سازی @notice و @param توسط Etherscan خوانده می‌شود و به کاربران نهایی نمایش داده می‌شود.

تصویر اتراسکن که natspec را نشان می‌دهد

می‌توانید در تصویر زیر از کد، ببینید که Etherscan این اطلاعات را از کجا آورده است:

تصویر تابع approve() با natspec در etherscan

پاکیزگی کلی کد (General Cleanliness)

14. حذف کد های کامنت شده

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

15. در انتخاب نام متغیرها با دقت فکر کنید

انتخاب نام مناسب برای متغیرها و توابع یکی از چالش‌برانگیزترین بخش‌های برنامه نویسی است، اما تأثیر چشم‌گیری بر خوانایی و نگه‌داری کد دارد. در ادامه، چند توصیه مهم برای نام‌گذاری آورده شده است:

  • از اسامی کلی و مبهم مانند user خودداری کنید. به جای آن از اسامی دقیق‌تری مانند admin، buyer یا seller استفاده کنید که نقش واقعی موجودیت را بهتر بیان می‌کنند.

  • واژه هایی مانند data معمولاً نشان‌دهنده نام‌گذاری مبهم هستند. به جای userData بهتر است از userAccount استفاده شود که مشخص‌تر است.

  • از به‌کارگیری اسامی متفاوت برای اشاره به یک مفهوم واحد بپرهیزید. مثلاً اگر depositor و liquidityProvider در واقع به یک موجودیت اشاره دارند، یکی را انتخاب کرده و در سراسر کد از همان استفاده کنید.

  • اگر متغیر عددی دارای واحد خاصی است، آن را در نام‌گذاری منعکس کنید. مثلاً به جای interestRate بنویسید interestRateBasisPoints یا feeInWei.

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

  • از نشانه‌گذاری (مثل زیرخط یا _underscore) به‌شکل یکسان و منسجم برای تمایز متغیرهای داخلی و آرگومان های ورودی استفاده کنید. مثلاً اگر قراردادی بین اعضای تیم وجود دارد که متغیرهای داخلی با زیرخط شروع می‌شوند (_internalVar)، در استفاده از آن استاندارد بمانید و آن را برای کاربردهای دیگر (مثلاً آرگومان هایی که با متغیرهای هم‌نام تداخل دارند) به کار نبرید.

  • استفاده از پیشوندهای get و set برای توابعی که به‌ترتیب فقط داده ها را نمایش می‌دهند یا وضعیت را تغییر می‌دهند، یک عرف رایج و پذیرفته‌شده در برنامه نویسی است. در صورت امکان، این رویه را رعایت کنید.

در پایان، پس از نوشتن کد، مدتی از آن فاصله بگیرید و بعد از حدود ۱۵ دقیقه، به آن بازگردید. سپس برای هر نام متغیر و تابع از خودتان بپرسید: «آیا این نام دقیق‌ترین و شفاف‌ترین انتخاب ممکن بوده است؟» این بازنگری آگاهانه، تأثیر بیشتری نسبت به هر چک‌لیستی خواهد داشت؛ چرا که شما بهتر از هر کسی از هدف و منطق پشت کدتان آگاه هستید.

ترفندهای اضافی برای سازمان دهی کدهای بزرگ در سالیدیتی

وقتی پروژه شما بزرگ‌تر می‌شود و کدها گسترده‌تر می‌شوند، داشتن ساختار منظم و مقیاس‌پذیر اهمیت زیادی پیدا می‌کند. در ادامه چند ترفند کاربردی برای مدیریت بهتر کدهای حجیم آورده شده است:

1. جدا کردن متغیرهای ذخیره سازی در یک قرارداد مستقل

اگر تعداد متغیرهای ذخیره سازی (Storage Variables) زیاد است، می‌توانید آن‌ها را در یک قرارداد جداگانه تعریف کرده و سپس در سایر قراردادها از آن ارث بری (inheritance) انجام دهید.
مثال:

2. استفاده از ساختار (struct) برای توابع با پارامترهای زیاد

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

3. تجمیع ایمپورت ها در یک فایل مشترک

اگر پروژه شما شامل ده ها فایل است و نیاز به ایمپورت های متعدد دارید، می‌توانید یک فایل مثلاً Imports.sol ایجاد کرده و تمام ایمپورت‌ها را در آن بنویسید. سپس در سایر فایل ها فقط آن فایل را import کنید. (البته این کار عمداً برخلاف قانون استفاده از ایمپورت های نام‌گذاری‌شده است.)

4. استفاده از کتابخانه ها (libraries) برای گروه‌بندی توابع مشابه

اگر چند تابع کاربردی مربوط به یک موضوع خاص دارید (مثل محاسبات مالی)، آن‌ها را در یک library تعریف کرده و در چندین قرارداد استفاده کنید. این کار باعث کاهش تکرار و افزایش خوانایی می‌شود.

جمع‌بندی

سازمان‌دهی کد در پروژه های بزرگ یک هنر است، نه صرفاً یک مهارت فنی. بهترین راه برای یادگیری آن، مطالعه پروژه های بزرگ و معتبر مانند Aave، Compound، Uniswap و OpenZeppelin است. این کار به شما دید عمیق‌تری درباره معماری کد، جداسازی مسئولیت ها و مدیریت وابستگی ها می‌دهد.

5/5 - (1 امتیاز)

راستی! برای دریافت مطالب جدید در کانال تلگرام یا پیج اینستاگرام سورس باران عضو شوید.

آموزش انیمیشن‌ سازی دو بعدی با موهو – خلق انیمیشن‌ های خلاقانه شبیه دیرین دیرین
  • انتشار: ۲۳ اردیبهشت ۱۴۰۴

دسته بندی موضوعات

آخرین محصولات فروشگاه

مشاهده همه

نظرات

بازخوردهای خود را برای ما ارسال کنید