سارا رضائی
سارا رضائی
خواندن ۱۰ دقیقه·۳ سال پیش

نام گذاری

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

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

تصویرسازی

هنگام انتخابِ نام، مثلا برای یک متغیر، هدف اصلی، ساخت یک تصویر در ذهن خواننده ی کد است. این تصویر باید تا حد امکان، به آن مفهوم نزدیک باشد. یک نام خوب، اطلاعات کافی درباره ی این که موجودیت مورد نظر چیست، و از آن مهم تر، چه چیزی نیست، در خود دارد.

هنگام انتخاب نام از خودتان بپرسید:

اگر کسی این نام را به تنهایی ببیند، بدون مشاهده ی مستندات کد، یا جایی که از آن استفاده شده، و بدون هیچ توضیحی، چقدر قادر خواهد بود حدس بزند که این نام، به چه چیز اشاره دارد؟ آیا نام دیگری وجود دارد که تصویر شفاف تری از این مفهوم ارائه دهد؟

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

نام ها، فرمی از انتزاع هستند. نام ها، یک راه ساده برای فکر کردن به یک موجودیت پیچیده فراهم می کنند. مانند سایر فرم های انتزاع، بهترین نام ها آن هایی هستند که بر روی آن جنبه هایی از موجودیت که مهم تر است تمرکز دارند و هر جزئیاتی که اهمیت کمتری دارد را حذف می کنند.




یک نام خوب، دو ویژگی دارد: دقت و ثبات

دقت:

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

  • مثال:
/** * Returns the total number of indexlets this object is managing. */ int IndexletManager::getCount() {...}

کلمه ی count خیلی عمومی است. منظور تعداد چه چیزی است؟ اگر کسی نام این متد را ببیند، بدون خواندن کامنت، یا خواندن پیاده سازی متد، نمی تواند بفهمد که این متد چه کاری انجام می دهد. تعداد چه چیزی را بر می گرداند؟ شاید یک نام بهتر برای این متد این باشد:

/** * Returns the total number of indexlets this object is managing. */ IndexletManager::getActiveIndexlets() {...}
  • مثال:
// Blink state: true when cursor visible. private boolean blinkStatus = true;

نام blinkStatus اطلاعات کافی به خواننده نمی دهد. کلمه ی status برای یک متغیر boolean خیلی مبهم است. این کلمه هیچ سرنخی درباره ی معنیِ مقادیر true و false نمی دهد. کلمه ی blink هم مبهم است، اصلا مشخص نیست که چه چیزی قرار است "چشمک بزند". همین مثال، به این صورت بهتر می شود:

private boolean cursorVisible = true;

نام cursorVisible اطلاعات بیشتری را فراهم می کند. به خواننده اجازه می دهد که به درستی حدس بزند، معنی true و false چیست.

  • مثال:
// Value representing that the server has not voted (yet) for // anyone for the current election term. private static final String VOTED_FOR_SENTINEL_VALUE = &quotnull&quot

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

private static final String NOT_YET_VOTED = &quotnull&quot
  • مثال:

در یک متد که خروجی ندارد، استفاده از یک متغیر به نام result، می تواند مشکل ایجاد کند. اول این که، این تصویرِ گمراه کننده را ایجاد می کند، که این متغیر، خروجی متد است. دوم این که، هیچ اطلاعاتی از این که این متغیر چه چیز را قرار است در خود داشته باشد، فراهم نمی کند. نام این متغیر، باید دقیقا بگوید، result چه چیز قرار است در آن قرار گیرد، برای مثال، mergedLine یا totalChars. البته اگر متد خروجی داشته باشد، استفاده از کلمه ی result برای خروجی متد قابل قبول است.

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

for  (i = 0; i < numLines; i++) { ... }

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

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

void delete(Range selection) {...}

نام selection، خیلی اختصاصی است. این نام تاکید بر این دارد که، متنی که قرار است حذف شود، حتما در UI توسط کاربر، select شده است. در حالی که این متد می تواند از جاهای دیگر هم صدا زده شود، چه متن را کاربر انتخاب کرده باشد یا نه. یک نام عمومی تر مثل range در این مثال، مناسب تر است.

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

اگر پیدا کردن یک نام ساده برای یک متغیر یا یک متد، که یک تصویر شفاف از موجودیت مورد نظر بسازد، سخت است، این نشانه ای است از اینکه آن موجودیت، طراحی مناسبی ندارد.

ثبات:

بعد از دقت، دومین ویژگیِ یک نام خوب، ثبات است. در هر نرم افزاری، متغیرهایی وجود دارند که بارها و بارها استفاده می شوند. مثلا کلمه ی block ممکن است در یک سیستم مدیریت فایل، با اهداف مختلف استفاده شود. ممکن است اشاره به یک block حافظه داشته باشد، یا یک block در یک فایل. در این مثال، باید جایی که block فایل مورد نظر است، از نامی مانند fileBlock استفاده شود. این کار باعث می شود "بار شناختی" کاهش یابد، چرا که اگر فرد یک بار این متغیر را ببیند، هر جای دیگر هم که آن را مشاهده کند، می داند مربوط به چه مفهومی است و نیازی نیست به تعریف متغیر رجوع کند.

ثبات در نام گذاری سه شرط اصلی دارد:

  1. همیشه برای یک هدف خاص، از یک نام مشترک استفاده شود.
  2. هیچ وقت آن نام مشترک، برای هدف دیگری به کار برده نشود.
  3. آن نام مشترک باید آن قدر مرتبط باشد، که همه ی متغیرهای با این نام، رفتار یکسانی داشته باشند.
در حلقه ها هم بهتر است به ثبات نام متغیر ها توجه کنید. اگر از اسامی i و j برای شمارنده ی حلقه استفاده می کنید، همیشه i برای حلقه ی بیرونی و j برای درونی باشد. این باعث می شود خواننده ی کد هر جا این متغیرها را دید، بداند منظور شمارنده ی کدام حلقه است.

یک دیدگاه متفاوت

بعضی از برنامه نویس های زبان Go معتقد هستند که نام ها باید بسیار کوتاه باشند، در حدِ فقط یک کاراکتر. در یک ارائه، درباره ی انتخاب نام در Go، آقای Andrew Gerrand می گوید:

"نام های طولانی، کاری را که کد انجام می دهد مبهم می کند."

او این مثال را می آورد:

func RuneCount(b []byte) int { i, n := 0, 0 for i < len(b) { if b[i] < RuneSelf { i++ } else { _, size := DecodeRune(b[i:]) i += size } n++ } return n }

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

func RuneCount(buffer []byte) int { index, count := 0, 0 for index < len(buffer) { if buffer[index] < RuneSelf { index++ } else { _, size := DecodeRune(buffer[index:]) index += size } count++ } return count }

و معتقد است که قطعه کد اول، با نام های تک کاراکتری، خوانایی بیشتری دارد.

در زبان Go، فرهنگ استفاده از نام های کوتاه، غالب است. مثلا به جای کلمه ی character یا channel از ch، به جای data یا difference یا distance از d استفاده می شود.

آیا این نام های کوتاه، بیشتر باعث مبهم شدن کد نمی شوند؟ آیا خواننده، برای این که بداند منظور از d چه چیزی است، نباید بیشتر تلاش کند؟

البته اگر قراردادی داشته باشیم، که مثلا همیشه n به count اشاره می کند، این ابهام برطرف می شود و در همه ی قسمت های نرم افزار، برنامه نویس ها به راحتی با دیدن n متوجه هدف و منظور آن قسمت از کد می شوند.

به طور کلی، میزان خوانایی کد، باید توسط خواننده ی کد تعیین شود نه نویسنده ی آن. اگر شما کدی با متغیرهای تک کاراکتری نوشتید و کسانی که آن را می خوانند به راحتی متوجه هدف و معنای نام گذاری ها می شوند، پس آن کد خوانایی بالایی دارد. ولی اگر خواننده ها شکایت کردند که این کد نیاز به رمزگشایی! دارد، باید در نام هایی که انتخاب کرده اید تجدید نظر کنید. حتی اگر از نام های طولانی استفاده کرده اید و این شکایت ها را گرفتید، باید به سمت نام های کوتاه تر بروید. هدف، بالا بودن میزان خوانایی کد است، چه این هدف با نام های تک کاراکتری براورده شود یا نام های طولانی. جایی مانند حلقه ها، ممکن است نام هایی مانند i و j پذیرفته شده باشد، و جایی، استفاده از حرف b برای اشاره به block، باعث ابهام در کد شود.

آقای Andrew Gerrand می گوید:

"هرچه فاصله بین تعریف یک نام و موارد استفاده آن بیشتر باشد، نام باید طولانی تر باشد."




خلاصه

نام گذاری مناسب، به واضح تر شدنِ کد کمک می کند؛ چرا که وقتی کسی برای اولین بار یک متغیر را می بیند، اولین حدس او درباره ی رفتار آن متغیر، به احتمال زیاد درست خواهد بود. انتخابِ نام خوب، یک مثال از سرمایه گذاری است که در برنامه نویسی استراتژیک و تاکتیکی مطرح شد. اگر کمی زمان بیشتر برای انتخاب نام مناسب بگذارید، در آینده، کار کردن روی کد راحت تر خواهد بود و زمان کمتری خواهد برد. همچنین احتمال خطا کمتر می شود.

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




منبع

A philosophy of software design

برنامه نویسیطراحی نرم افزارتوسعه نرم افزارنام گذاری
linkedin.com/in/sara-rez
شاید از این پست‌ها خوشتان بیاید