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

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

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

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

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

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

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

برنامه نویس ها باید بتوانند، بدون خواندن پیاده سازی یک ماژول، و فقط از طریق واسط (یعنی آنچه که در اختیار دنیای بیرون قرار گرفته)، انتزاعِ فراهم شده توسط آن ماژول را بفهمند.

تنها راهِ انجام این مهم، تکمیلِ نمای بیرونی ماژول، با کامنت است.




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

اگر از زبان های برنامه نویسی استفاده می کنید، که برای آن ها ابزار مستندسازی وجود دارد، مثل Javadoc برای Java یا godoc برای Go، بهتر است از چهارچوب و قوانین همان ابزار تبعیت کنید، و اگر هیچ ابزاری ندارید، باید قراردادهای اختصاصی خودتان را مشخص کنید.

قراردادها از دو جنبه به ما کمک می کنند:

1. ثبات و هماهنگی فراهم می کنند و این باعث می شود خواندن و فهمیدن کامنت ها ساده تر باشد.

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




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

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

2. نوع دوم از کامنت ها، دقیقا قبل از تعریف یک عضو از ماژول قرار می گیرند. مثلا در یک کلاس، برای هر کدام از instance variable ها یا فیلد های static کلاس ممکن است توضیحی قبل از آن بنویسیم.

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

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

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

مثلا در یک فایل با نام theFile این توضیح را می نویسیم:

Zombies ------- A zombie is a server that is considered dead by the rest of the cluster; any data stored on the server has been recovered and will be managed by other servers.

و در ماژول ها:

// See &quotZombies&quot in theFile.



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

مثال:

ptr_copy = get_copy(obj) # Get pointer copy if is_unlocked(ptr_copy): # Is obj free? return obj # return current obj if is_copy(ptr_copy): # Already a copy? return obj # return obj thread_id = get_thread_id(ptr_copy) if thread_id == ctx.thread_id: # Locked by current ctx return ptr_copy # Return copy

در این قطعه کد، کامنت ها (که با # مشخص شده اند) هیچ اطلاعات مفیدی در خود ندارند. فقط هر آنچه در کد بوده، در کامنت کپی شده است.

برای فهمیدن این که کامنت مفید است یا نه، بعد از این که کامنت را نوشتید، این سوال را از بپرسید:

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

یک اشتباه دیگر این است که در کامنت، دقیقا همان کلماتی را استفاده کنیم که در نام ماژول استفاده شده است. این نوع کامنت ها هم هیچ ارزشی ایجاد نمی کنند. مثلا:

/* * Obtain a normalized resource name from REQ. */ private static String[] getNormalizedResourceNames(HTTPRequest req)

در این مثال، اطلاعات مهم تری هست که می توانست در کامنت قرار داده شود. مثلا این که منظور از عبارت normalized resource name چیست؟ توضیح دادن این نکته، در این کامنت، می توانست آن را به یک کامنت مفید تبدیل کند.

یک مثال دیگر، این کامنت، یک کامنت بی فایده است:

/* * The horizontal padding of each line in the text. */ private static final int textHorizontalPadding = 4;

ولی همین کامنت، می تواند به این صورت بهتر شود:

/* * The amount of blank space to leave on the left and * right sides of each line of text, in pixels. */ private static final int textHorizontalPadding = 4;

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




علاوه بر تقسیم بندی که بالاتر گفته شد، در یک نمای کلی تر، می توان کامنت ها را به دو نوع تقسیم کرد:

1. کامنت های سطح پایین، که جزئیات بیشتری از کد را آشکار می کنند.

این کامنت ها، معمولا در تعریف متغیرها، پارامترهای متدها یا خروجی آن ها استفاده می شوند و جزئیاتی مانند این ها را آشکار می کنند:

  • برای یک متغیر، واحد اندازه گیری آن چیست؟ مثلا در مورد متغیری که زمان را در خود نگه می دارد، آیا ثانیه است، دقیقه است یا میلی ثانیه؟
  • اگر برای یک متغیر، مقدار null قابل قبول است، معنی آن چیست؟ در صورتی که null باشد، چه شرایطی پیش می آید؟
  • اگر در استفاده از یک متغیر، resource خاصی وجود دارد که باید بعد از استفاده آزاد شود، مسئول آزاد کردن آن، چه بخشی از کد است؟
  • آیا اقدام خاصی هست که در استفاده از یک متغیر باید لحاظ شود؟ مثلا این که یک لیست، حتما باید حداقل یک آیتم در خودش داشته باشد

یک مثال از کامنتِ سطح پایینِ بد:

// Current offset in resp Buffer uint32_t offset;

و همان کامنت به این صورت می تواند بهتر شود:

// Position in this buffer of the first object that hasn't // been returned to the client. uint32_t offset;

در حالت اول، مشخص نیست منظور از Current چیست. در واقع این کامنت می توانست با تغییر نام متغیر offset به currentOffset حذف شود. ولی در حالت دوم، توضیحی که کامنت می دهد، چیزی است که کد قادر به گفتن آن نیست.

2. کامنت های سطح بالا، که درک شهودی بیشتری به برنامه نویس می دهند. این کامنت ها جزئیات را حذف می کنند و به خواننده ی کد کمک می کنند که درک بهتری از ساختار و مفهوم کد داشته باشد.

یک مثال از کامنت سطح بالای بد:

// If there is a LOADING readRpc using the same session // as PKHash pointed to by assignPos, and the last PKHash // in that readRPC is smaller than current assigning // PKHash, then we put assigning PKHash into that readRPC. int readActiveRpcId = RPC_ID_NOT_ASSIGNED; for (int i = 0; i < NUM_READ_RPC; i++) { if (session == readRpc[i].session && readRpc[i].status == LOADING && readRpc[i].maxPos < assignPos && readRpc[i].numHashes < MAX_PKHASHES_PERRPC) { readActiveRpcId = i; break; } }

چرا این کامنت بد است؟ چون خط به خط کد را توضیح داده و در واقع تکرار کد است.

نسخه ی بهتر همان کامنت:

// Try to append the current key hash onto an existing // RPC to the desired server that hasn't been sent yet.

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

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

این کد چه کاری می خواهد انجام دهد؟

ساده ترین جمله ای که می تواند همه چیز را در مورد این کد توضیح دهد چیست؟

مهم ترین مساله درباره ی این کد چیست؟

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


خلاصه

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

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

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


منبع

A philosophy of software design

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