هنر کدنویسی خوانا: چه چیزی را کامنت کنیم

واقعیتش من چند وقتی بود که داشتم کتاب “هنر کدنویسی خوانا” یا همان The Art of Readable Code را می‌خوندم و دیدم خیلی کتاب مفیدی هست و بد نیست که برنامه‌نویسان عزیز این کتاب را بخوانند، برای همین تصمیم گرفتم کتاب را ترجمه کنم و چند فصل از کتاب را هم اینجا منتشر کنم. امیدوارم مفید باشه و برنامه نویسان عزیزی مثل شما بعد از خوندن این کتاب کدهای تمیزتری بنویسید و کمی به شما در مسیر پیشرفت توی حوزه برنامه‌نویسی کمک کند.
خب در این پست فصل پنجم کتاب هنر کدنویسی خوانا را با موضوع چه چیزی را کامنت کنیم، با هم دنبال می‌کنیم:

 

 

فصل پنجم: چه چیزی را کامنت کنیم!

 

 

 

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

کلید طلایی

هدف از نوشتن کامنت کمک به خواننده است تا به همان اندازه که نویسنده کد آن را درک کرده، خواننده نیز کد را بفهمد.

 

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

در این فصل مثال‌های زیادی را در این مورد که چه زمانی این اطلاعات داخل مغزتان را به شکل کامنت بنویسید، به شما نشان خواهیم داد. البته به مواردی که جذابیت کمتری دارند، نمی‌پردازیم، در عوض بر روی جنبه‌های جالب‌تر و «مورد توجه قرار نگرفته[1]» از کامنت گذاری متمرکز می‌شویم.

در این فصل به سه بخش مهم خواهیم پرداخت:

  • دانستن اینکه چه چیزی را کامنت نکنیم.
  • ثبت افکارتان همانند کدنویسی‌تان.
  • قرار دادن خودتان به جای خواننده، تا تصور کنید که چه چیزی را باید بداند.

 

 

چه چیزی را نباید کامنت کنیم

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

تمام کامنت‌های کد زیر بی ارزش هستند:

// The class definition for Account
class Account {
    public:
      // Constructor
      Account();
      // Set the profit member to a new value
      void SetProfit(double profit);
      // Return the profit from this Account
      double GetProfit();
  };

زیرا هیچ اطلاعات جدیدی را ارائه نداده و هیچ کمکی برای درک بهتر کد به خواننده نمی‌کنند.

کلید طلایی

در مورد حقایقی که خودشان می‌توانند سریعا[1] با خواندن کد به دست آیند، کامنت ننویسید.

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

# remove everything after the second '*'

name = '*'.join(line.split('*')[:2])

 

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

فقط برای اینکه کدتان کامنت داشته باشد! کامنت گذاری نکنید

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

// Find the Node in the given subtree, with the given name, using the given depth.

Node* FindNodeInSubtree(Node* subtree, string name, int depth);

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

اگر می‌خواهید کامنتی در اینجا داشته باشید، می‌توانید در مورد جزئیات مهم‌تر نیز توضیح دهید:

// Find a Node with the given 'name' or return NULL.

// If depth <= 0, only 'subtree' is inspected.

// If depth == N, only 'subtree' and N levels below are inspected.

Node* FindNodeInSubtree(Node* subtree, string name, int depth);

برای نام‌های بد کامنت ننویسید، در عوض، نام‌های بهتری انتخاب کنید

لازم نیست یک کامنت، بد بودن یک نام را جبران کند. برای مثال، در اینجا یک کامنت بی فایده برای تابعی به نام CleanReply() نوشته شده است:

// Enforce limits on the Reply as stated in the Request,

// such as the number of items returned, or total byte size, etc.

void CleanReply(Request request, Reply reply);

بخش اعظم این کامنت در حال توضیح این مطلب است که معنی clean چیست. کافی است به جای آن عبارت «enforce limits» به نام تابع اضافه شود:

// Make sure 'reply' meets the count/byte/etc. limits from the 'request'

void EnforceLimitsFromRequest(Request request, Reply reply);

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

در ادامه مثال دیگری از استفاده کامنت به دلیل انتخاب یک نام ضعیف برای یک تابع را مشاهده می‌کنید:

// Releases the handle for this key. This doesn't modify the actual registry.

void DeleteRegistry(RegistryKey* key);

به نظر می‌رسد نام DeleteRegistry() شبیه یک تابع خطرناک است(این تابع حافظه registery را delete می‌کند؟!). کامنت نوشته شده به معنی «در واقع این رجیستری را تغییر نمی‌دهد» است و تلاش می‌کند که از گنگ بودن نام تابع، رفع ابهام کند.

به جای آن می‌توانیم از یک نام خود-مستندساز[1] بهتر به صورت زیر استفاده کنیم:

void ReleaseRegistryHandle(RegistryKey* key);

به طول کلی، شما نمی‌خواهید کامنت‌های crutch[2] داشته باشید. کامنت‌های crutch، کامنت‌هایی هستند که سعی می‌کنند ناخوانا بودن کد شما را جبران کنند. کدنویس‌ها اغلب این وضعیت را به شکل یک قانون، این گونه بیان می‌کنند که: «کد خوب بهتر از کد بد + کامنت خوب[3]» است.

ثبت افکار خود

حال که فهمیدید چه چیزی را کامنت نکنید، بیایید در این مورد که چه چیزی را باید کامنت کنیم، بحث کنیم.

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

افزودن «توضیحات کارگردان»

فیلم‌ها اغلب بخشی با عنوان «توضیحات کارگردان» دارند که در آن فیلم‌ساز بینش خود را ارائه داده و داستان‌هایی را برای کمک به فهمیدن اینکه این فیلم چطور ساخته شده است، بیان می‌کند. به طور مشابه، شما نیز باید کامنت‌ها را برای ثبت دیدگاه‌های با ارزش درباره کد اضافه کنید.

به عنوان مثال این کامنت:

// Surprisingly, a binary tree was 40% faster than a hash table for this data.

// The cost of computing a hash was more than the left/right comparisons.

 

چیزهایی را به خواننده یاد داده و از هرگونه اتلاف وقت آنان جلوگیری می‌کند.

به عنوان مثال دیگر کامنت زیر را در نظر بگیرید:

// This heuristic might miss a few words. That's OK; solving this 100% is hard.

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

همچنین یک کامنت می‌تواند دلیل عالی نبودن شکل ظاهری کد را شرح دهد:

// This class is getting messy. Maybe we should create a 'ResourceNode' subclass to

// help organize things.

این کامنت می‌گوید که کد ما کثیف است اما همچنان نفر بعدی را برای ترمیم آن تشویق می‌کند(با ارائه مشخصاتی در مورد چگونگی بهبود آن).

با نبود هیچ کامنتی، خیلی از خواننده‌ها با دیدن کدهای کثیف دچار ترس شده و از دست زدن به آن‌ها خودداری می‌کنند.

نقص‌های موجود در کد خود را کامنت کنید

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

// TODO: use a faster algorithm

یا هنگامی که کد کامل نشده است:

// TODO(dustin): handle other image formats besides JPEG

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

ممکن است تیم شما قراردادی برای زمان یا شرط استفاده از این نشانه‌ها داشته باشد. به عنوان مثال TODO: ممکن است برای نشان دادن توقف اشکالاتِ[1] رزرو شده باشد. اگر چنین است، پس برای نقص‌های جزئی‌تر می‌توانید بجای TODO: از چیزی شبیه todo: (با حروف کوچک) و یا maybe-later: استفاده کنید.

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

در مورد ثابت‌ها کامنت بنویسید

هنگام تعریف یک ثابت، غالبا این سوال وجود دارد که چرا این متغیر ثابت، مقدار خاصی دارد و مقدار خاص آن چیست؟ به عنوان مثال ممکن است چنین ثابتی را در کد خود ببینید:

NUM_THREADS = 8

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

NUM_THREADS = 8  # as long as it's >= 2 * num_processors, that's good enough.

با این کامنت، شخصی که کد را می‌خواند یک راهنما در مورد نحوه تنظیم این متغیر ثابت دارد(به عنوان مثال، تنظیم آن به مقدار ۱ یا ۵۰ احتمالا به ترتیب خیلی کم یا بیش از حد زیاد است).

گاهی اوقات مقدار دقیق یک ثابت به هیچ وجه مهم نیست که در این موارد، وجود یک کامنت، مفید به نظر می‌رسد:

// Impose a reasonable limit - no human can read that much anyway.

const int MAX_RSS_SUBSCRIPTIONS = 1000;

گاهی نیز این ثابت دارای مقداری است که بسیار دقیق تنظیم شده است و احتمالا نباید خیلی تغییر داده شود:

image_quality = 0.72;  // users thought 0.72 gave the best size/quality tradeoff

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

خودتان را جای خواننده بگذارید

یک تکنیک کلی که در این کتاب استفاده می‌کنیم این است که «تصور کنید کد شما در نظر یک شخص خارجی چگونه به نظر می‌رسد»، کسی که به اندازه شما با این پروژه آشنا نیست. این تکنیک به ویژه برای تشخیص اینکه چه بخش‌هایی نیاز به کامنت گذاری دارند به شما کمک می‌کند.

پیش بینی سوالات احتمالی

هنگامی که شخص دیگری کد شما را بخواند، بخش‌هایی وجود دارد که ممکن است در مورد بخش‌هایی از آن به این صورت فکر کند که «هااااااا؟ اینا اصلا در مورد چی هستند؟» کار شما این هست که این بخش‌ها را کامنت گذاری کنید. به عنوان مثال نگاهی به تعریف تابع Clear() بیندازید:

struct Recorder {

    vector<float> data;

    ...

    void Clear() {

        vector<float>().swap(data);  // Huh? Why not just data.clear()?

    }

};

اکثر برنامه‌نویسان C++ در هنگام مواجه با این کد به این فکر خواهند کرد که: چرا در آن به جای تعویض[1] با یک بردار[2] خالی، فقط از data.clear()‌ استفاده نشده است؟ خب معلوم است که این تنها روش مجبور کردن یک بردار است، تا به درستی حافظه خود را به حافظه allocator[3] رها سازی کند. این کار از جزئیات C++ است که به خوبی شناخته نشده است. بنابراین، باید اینگونه کامنت گذاری شود:

// Force vector to relinquish its memory (look up "STL swap trick")

vector<float>().swap(data);

 

اعلام کردن تله احتمالی

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

به عنوان مثال، فرض کنید تابعی نوشته‌اید که یک ایمیل را به کاربری معین ارسال می‌کند:

void SendEmail(string to, string subject, string body);

پیاده سازی این تابع شامل اتصال به یک سرویس ایمیل خارجی است که ممکن است یک ثانیه کامل یا بیشتر زمان ببرد. شخصی که در حال نوشتن یک برنامه کاربردی وب[1] است ممکن است متوجه این موضوع نشده و اشتباها این تابع را حین انجام درخواست[2] HTTP فراخوانی کند که در صورت قطع سرویس ایمیل، انجام این کار سبب توقف[3] برنامه کاربردی وب می‌گردد.

برای جلوگیری از این اشتباه احتمالی، باید در مورد جزئیات پیاده سازی شده کامنت گذاری کنید:

// Calls an external service to deliver email.  (Times out after 1 minute.)

void SendEmail(string to, string subject, string body);

در اینجا مثال دیگری داریم، فرض کنید تابع FixBrokenHtml() را دارید که تلاش می‌کند کدهای HTML دارای اشکال را با افزودن تگ بسته شدن به انتهای آن‌ها، بازنویسی کند:

def FixBrokenHtml(html): ...

این تابع در غیر از مواردی که حین اجرای آن تگ‌های بدون همتا[4] و تو در توی بسیار زیادی وجود داشته باشد، خیلی عالی عمل می‌کند ولی برای ورودی‌های بهم ریخته و کثیف HTML اجرای این تابع می‌تواند چند دقیقه طول بکشد.

به جای اینکه اجازه دهید کاربر خودش بعدها متوجه این موضوع شود، بهتر است که در یک کامنت و در همان ابتدای کار در مورد سرعت اجرای آن هشدار دهید:

// Runtime is O(number_tags * average_tag_depth), so watch out for badly nested inputs.

def FixBrokenHtml(html): ...

 

کامنت‌های روند کلی

 

یکی از سخت‌ترین موارد برای عضو جدید تیم، فهمیدن روند کلی برنامه است. اینکه تعامل کلاس‌ها چگونه است، جریان داده در کل سیستم به چه صورت بوده و نقاط ورودی[1] کجاها هستند.

طراح اصلی سیستم به دلیل درگیری زیاد با این موارد معمولا فراموش می‌کند که درباره آن‌ها کامنت گذاری کند.

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

شما در حال ارائه توضیحات در مورد کدپایه، ممکن است به فایل‌ها و کلاس‌های خاصی اشاره کنید و چیزی شبیه این جملات را بگویید:

  • این کد واسط[2] بین منطق بیزینس ما و دیتابیس است. هیچ کد اپلیکیشنی نباید از این کد مستقیما استفاده کند.
  • این کلاس به نظر پیچیده می‌رسد، اما این در واقع یک Cache هوشمند[3] است و در مورد بقیه سیستم چیزی نمی‌داند.

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

این دقیقا همان نوع اطلاعاتی است که باید به کامنت‌های سطح بالا[4] اضافه شود.

در اینجا یک مثال ساده از کامنت سطح-فایل[5] داریم:

// This file contains helper functions that provide a more convenient interface to our

// file system. It handles file permissions and other nitty-gritty details.

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

کامنت‌های خلاصه

این ایده خوبی است که حتی در عمق یک تابع، در مورد تصویر کلی‌تر آن، کامنت گذاری کنید. در اینجا مثالی از یک کامنت آورده شده است که کد سطح-پایین[6] زیر آن را به صورت خلاصه بیان می‌کند:

# Find all the items that customers purchased for themselves.

for customer_id in all_customers:

    for sale in all_sales[customer_id].sales:

        if sale.recipient == customer_id:

            ...

بدون این کامنت، خواندن هر خط از کد مقداری رمزآلود خواهد شد چرا که به نظر می‌رسد از طریق all_customers … در حال تکرار هستیم و این سوال پیش می‌آید که «این تکرار برای چیست؟».

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

def GenerateUserReport():

    # Acquire a lock for this user

    ...

    # Read user's info from the database

    ...

    # Write info to a file

    ...

    # Release the lock for this user

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

دلایل کامنت گذاری: برای چه چیزی؟ چرا؟ یا چگونه؟

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

افکار نهایی، گذر از بلوک نویسنده

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

به عنوان مثال فرض کنید شما روی یک تابع کار می‌کنید و با خود می‌اندیشید که: لعنت بهش[9]، اگر تکراری در این لیست وجود داشته باشد، این چیزها[10] دچار مشکل خواهد شد. فقط همین را به عنوان کامنت بنویسید:

// Oh crap, this stuff will get tricky if there are ever duplicates in this list.

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

  • کلمه‌ای با معنی واقعی‌تر برای لعنت بهش! همچون این عبارت: مراقب باشید: این چیزی است که باید بیشتر مراقب آن باشید.
  • کلمه‌ای با معنی دقیق‌تر نیز برای this stuff همچون این عبارت: کدی که این ورودی را مدیریت می‌کند.
  • عبارتی رساتر برای اجرای آن دشوار[11] خواهد بود همچون این عبارت: پیاده سازی آن سخت خواهد بود.

احتمالا کامنت جدید به این صورت خواهد بود:

// Careful: this code doesn't handle duplicates in the list (because that's hard to do)

توجه داشته باشید که ما نوشتن یک کامنت را به سه مرحله ساده تقسیم کردیم:

  1. هرچیزی که به عنوان کامنت در مغز شما است را بنویسید.
  2. کامنت را بخوانید، و هر چیزی را که نیازمند بهبود است، بهبود دهید.
  3. کل کامنت را بهبود دهید.

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

 

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد.