4.19 اصول کامنت نویسی

4.19 اصول کامنت نویسی

اصول کامنت‌نویسی در زبان گو

4.19.1 تعریف #

«کامنت» به توضیحات‌ی گفته می‌شود که داخل کد، به‌قصد «یادآوری»، «جلب‌توجه مخاطب به موضوع‌(ها)ی خاص»، کمک به onboard شدن اعضاء جدید تیم و مانند اون نگارش می‌شود. در زبان گو، به شیوه‌های زیر کامنت می‌گذاریم:

  • قرار دادن // در ابتدای سطر.

    1// defined to store multi string (see below why this is not a good comment)
    2var sliceVar []string
    
  • قراردادن متن کامنت داخل یک بلوک که با */ شروع می‌شود و /* تمام می‌شود.

    1for index, row := range sliceVar {  
    2   row /* for loop lifetime */ = "new_val"  
    3   sliceVar[index] /* apply to sliceVar out of loop */ = "new_val"  
    4   fmt.Println(row) // hint to sliceVar large len  
    5}
    

دستور ‍gofmt کامنت‌ها را مانند سایر فرمت‌ها، مرتب می‌نماید. اما روش بهتر درصورت امکان، نظم آنها توسط خود توسعه‌دهنده جهت رعایت الگوها و ساختار خاص هر پروژه است.

1gofmt -w main.go

4.19.2 دیدگاه‌ها درباره «کامنت» #

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

4.19.3 کامنت؛ خوب، بد، زشت #

  • در کدهایی که بارها نسخه‌های متفاوتی از آن ایجاد شده و در طول زمان، نیازمندی‌ها عوض شده، کیفیت، کارایی و سرعت اجرا بهبود پیدا کرده، «کامنت» گزارش «چرایی» کد هست برای این: نیاز/کیفیت/کارایی/سرعت اجرا، برای اینکه همه این‌ها رو دوباره تجربه نکنند ...
  • یک کد خوب، هیچ نیازی به کامنت ندارد، به‌زبان‌دیگر، اگر نیاز می‌بینید که برای کدی «کامنت» بنویسید، احتمالاً، کد خوبی ننوشتید ...
  • یک ساختار جدید، ناشناخته و احتمالاً حجیم، به‌قدر‌کافی ماهیتاً اینقدر پیچیدگی دارد که اضافه شدن، یک توضیح به زبان کاملاً انسانی (داخل زبان کامپایلر/مفسری برای زبان ماشین)، نه‌تنها باعث روشن‌تر شدن آن نمی‌شود بلکه مسئله‌ی فهم منظور نگارنده «کامنت» به مجموعه مسائل قبلی اضافه می‌گردد. هیچ‌چیز بیشتر از یک کد پیچیده با کلی «کامنت‌های» پیچیده برای مخاطبی که انتظار روشن بودن چرایی و چگونگی کد را دارد، عذاب‌آور نیست ...

همه این‌ها پاسخ‌های متفاوت‌ی است که توسعه‌دهندگان به موضوع «کامنت» می‌دهند. اما «اصولاً» کامنت پرفایده است یا بی‌فایده؟

4.19.4 آنالیز محصول و محیط توسعه #

وقتی در ارتباط با کامنت صحبت می‌کنیم این خیلی مهم است که ما به‌تنهایی مشغول توسعه یک محصول هستیم یا در یک دپارتمان کوچک یا در یک ابَرپروژه … آیا ما مجبور به تبعیت از یک‌سری دستورالعمل‌های کدنویسی هستیم یا می‌توانیم سلیقه‌شخصی خود را داشته باشیم؟ …

  • شرایط تیم توسعه.
    • نحوه مدیریت(افراد/روش‌ها) پروژه در فرآیند توسعه کد.
    • تعداد زیرمجموعه‌ها و تعداد توسعه‌دهندگان در بخش‌های مختلف.
    • میزان ارتباط و حساسیت کدها بین واحدها و توسعه‌دهندگان.
    • سرعت تغییرات جابجایی توسعه‌دهندگان در پروژه.
    • و موارد مشابه دیگر.
  • تحلیل نیازمندی‌های محصول.
    • مقیاس پروژه.
    • زمان توسعه پروژه.
    • زمان تغییرات همزمان با نسخه‌های ریلیز شده.
    • پیچیدگی و ماهیت نیازهای محصول.
    • و موارد مانند این‌ها.
      نتیجه اینکه: ابتدا نیازمندی، توانایی و شرایط تیم/محصول را مشخص کنیم، و بعد تصمیم به چرایی و چگونگی کامنت‌نویسی اصولی بگیریم.

4.19.5 انواع کامنت #

  • کامنت فایل/پکیج (Doc Comment)

این‌نوع کامنت‌ها درباره «چیستی» کل فایل یا پکیج توضیح دارد.

 1// Copyright 2011 The Go Authors. All rights reserved.  
 2// Use of this source code is governed by a BSD-style  
 3// license that can be found in the LICENSE file.  
 4  
 5/*  
 6Package builtin provides documentation for Go's predeclared identifiers.  
 7The items documented here are not actually in package builtin  
 8but their descriptions here allow godoc to present documentation  
 9for the language's special identifiers.  
10*/  
11package builtin

مثال بالا از پکیج builtin درباره حق‌چاپ / تعریف اولیه پکیج و اینکه مستندات در godoc ارائه می‌شود، توضیح داده است.

  • کامنت داخلی فانکشن/متد/بلوک/تایپ/متغیر/دستور و مانند آن (Ordinary Comments) این‌نوع کامنت درباره «چرایی» آن قسمتِ خاص اشاره دارد.
1// The delete built-in function deletes the element with the specified key  
2// (m[key]) from the map. If m is nil or there is no such element, delete  
3// is a no-op.  
4func delete(m map[Type]Type1, key Type)

در مثال بالا، توسط کامنت توضیح داده شده که وظیفه فانکشن-داخلی delete حذف المنت با کلید مشخص هست، و توضیح دقیق‌تر اینکه اگر المنت مربوط به کلید nil باشد یا وجود نداشته باشد، فانکشن delete هیچ عملیاتی انجام نمی‌دهد. (مثلاً خطا باز نمی‌گرداند − گزارش نمی‌کند و …)

4.19.6 اصول کامنت‌نویسی #

یک کامنت خوب:

  1. توضیح واضحات را نمی‌دهد.
  2. در حداقل مقدار «لازم» و «کافی» نگارش می‌شود.
  3. بیشتر درباره «چیستی/چرایی» اشاره دارد و نه «چگونگی».
  4. دارای یک الگو و دستورالعمل نگارشی واحد برای نظم و سرعت ارتباط مخاطب است.
  5. وجودش آگاه‌کننده موضوع بااهمیت بالاست.
  6. مربوط به موضوعی است که اکنون وجود دارد (بروزرسانی کامنت‌ها-حذف کامنت‌های اضافی)
  7. ادبیات کامنت، بسته به تیم و دستورالعمل‌ها، بهتر است رسمی نگارش شود تا عمومی بماند. البته گاهی کمی شوخ‌طبعی هم اگر کنترل‌شده باشد، باعث انتقال‌مطلب بهتر می‌شود.
  8. درصورت لازم بودن یک یا چند منبع مرتبط با کد، حاوی لینک url خواهد بود.
    1 	// flip the buffer for this connection if we need to drain it.  
    2 	// note that for a successful query (i.e. one where rows.next()  
    3 	// has been called until it returns false), `rows.mc` will be nil  
    4 	// by the time the user calls `(*Rows).Close`, so we won't reach this  
    5 	// see: https://github.com/golang/go/commit/651ddbdb5056ded455f47f9c494c67b389622a47  
    6 	mc.buf.flip()
    

4.19.7 به پرتگاه نزدیک می‌شوید! #

  • زامبی کد: به کدی می‌گویند که به دلیل عدم کارایی، اصلاح با کد جدید، و یا مشابه این موارد، بجای «حذف»، «کامنت» می‌شوند.
  • کامنت اسپاگتی کد: به کامنت‌های دنباله‌داری گفته می‌شود که برای توضیح یک کدی که ساختار منظم و مشخصی ندارد، نگارش می‌شود.
  • یکی دیگر از استفاده‌های کامنت، وظیفه‌ی برنامه‌ریزی‌شده می‌باشد که اگر کنترل نشود، یکی دیگر از عذاب‌های عظیم خواهد بود.
  • جای کلمات عبور و مقادیر امنیتی در کامنت نیست.
  • اگر دائماً نیاز می‌بینید که در مراحل مختلف به همکاران بصورت کامنت «هشدار» بنویسید، شاید باید به‌فکر اصلاح معماری نرم‌افزار باشید.
  • کامنت‌های شما، نباید تبدیل به «نویز» درکدنویسی دیگران شود. تعدد کامنت‌ها کد را تبدیل به کد کثیف می‌کند که خوانایی ضعیفی خواهد داشت.
  • کامنت، جای دردل کردن، شکایت از مدیرپروژه، تعریف از خود و گفتگو نیست.

4.19.8 انواع directive comment #

//go:generate: این کامنت برای مشخص کردن یک دستور است که باید توسط ابزار go generate اجرا شود. این کامنت معمولاً قبل از یک دستور تولید کد قرار داده می‌شود که به شما اجازه می‌دهد کد Go را به صورت خودکار تولید کنید.

//go:binary-only-package: این کامنت برای اعلام این استفاده می‌شود که یک بسته باید به عنوان یک بسته فقط دودویی در کامنت گرفته شود، به معنای این است که کد منبع بسته در دسترس نیست. این برای بسته‌هایی استفاده می‌شود که شامل کد‌های محصولی یا بسته‌های مشخص سیستم‌عامل هستند.

//go:build: این کامنت برایمحدودیت‌های ساخت استفاده می‌شود. این به شما امکان می‌دهد که کنترل کنید که یک فایل باید بر اساس شرایط خاصی مانند سیستم عامل، معماری یا برچسب ساخت، در ساخت شامل شود یا خیر.

//go:cgo_…: چندین کامنت دستوری وجود دارد که با cgo_ شروع می‌شوند، مانند //go:cgo_import_dynamic و //go:cgo_export_dynamic. این کامنتات همراه با cgo استفاده می‌شوند، ابزاری که به کد Go اجازه می‌دهد تا به کد C و بالعکس برای فراخوانی دستورات استفاده شود. آن‌ها دستوراتی را به ابزار cgo ارائه می‌دهند که نحوه برخورد کد C را مشخص می‌کنند.

//go:noinline: این کامنت برای مشخص کردن این استفاده می‌شود که یک تابع توسط کامپایلر به صورت inline نباید درج شود. Inline کردن یک تابع یک تکنیک بهینه‌سازی است که کد یک تابع به طور مستقیم در کد فراخواننده آن قرار می‌گیرد و هزینه فراخوانی تابع را حذف می‌کند. استفاده از این کامنت از کامپایلر جلوگیری می‌کند تا برای تابع مشخص شده inline کردن انجام دهد.

//go:nosplit: این کامنت برای مشخص کردن این استفاده می‌شود که یک تابع باید توسط برنامه‌ریز Go runtime اجرا شده (split) نشود. این معمولاً برای توابع سطح پایین استفاده می‌شود که نیاز به کنترل دقیق بر روی اجرای آن‌ها دارند و نباید وقفه داده شوند.

//go:linkname: این کامنت برای برقراری ارتباط بین کد Go و نمادهای خارجی یا کد غیر Go استفاده می‌شود. این به شما اجازه می‌دهد تا به یک نماد با نام متفاوت یا از بسته‌ای دیگر ارجاع دهید.

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

//go:embed: این کامنت برای اضافه کردن فایل‌های استاتیک یا دایرکتوری‌ها به طور مستقیم به باینری Go در زمان کامپایل استفاده می‌شود. این فرآیند از جمله فرآیند بسته‌بندی و توزیع منابع با برنامه‌های Go خود است.

//go:generate go run: این کامنت یک نوع دیگر از کامنت //go:generate است. این کامنت مشخص می‌کند که دستور زیر کامنت باید توسط اجرای برنامه Go با استفاده از دستور go run اجرا شود.

//go:build …: این کامنت یک فرم گسترده‌تر از کامنت build است. این به شما امکان می‌دهد شرایط ساخت را با استفاده از اپراتورهای منطقی بولی، پرانتز و نفی مشخص کنید. این امکانات بیشتری در کنترل کردن فایل‌هایی که در ساخت شامل می‌شوند، فراهم می‌کند.

//go:protofile: این کامنت برای مشخص کردن پروتوباف فایل مرتبط با یک فایل منبع Go استفاده می‌شود. این معمولاً در کد Go استفاده می‌شود که شامل کد پروتوباف تولید شده است، اجازه می‌دهد که کامپایلر فایل‌های Go و پروتوباف رابه درستی به هم پیوند دهد.

//go:nowritebarrier: این کامنت برای مشخص کردن این استفاده می‌شود که یک تابع بدون write barrier باید اجرا شود. Write barrier برای تعقیب و به‌روزرسانی اشاره‌گرها در زمان تخصیص و آزادسازی حافظه توسط garbage collector استفاده می‌شود. استفاده از این کامنت ممکن است خطرناک باشد و تنها در موارد خاصی که مدیریت دستی حافظه لازم است، باید استفاده شود.

//go:norace: این کامنت دستوری برای غیرفعال کردن ردیابی race برای یک تابع خاص است. ردیاب race یک ابزار در Go است که به شناسایی دسترسی همزمان به متغیرهای مشترک که ممکن است منجر به دور زدن داده‌ها (race condition) بشود، کمک می‌کند. این دستور می‌تواند هنگامی استفاده شود که از عدم وجود شرایط race برای یک تابع خاص اطمینان دارید.

//go:buildignore: این کامنت دستوری برای حذف یک فایل از فرآیند ساخت استفاده می‌شود. این به سیستم ساخت Go می‌گوید که این فایل را نادیده بگیرد و در هنگام کامپایل بسته، شامل نشود.

//go:generate goimports: این کامنت دستوری برای استفاده در ارتباط با دستور //go:generate استفاده می‌شود تا به صورت خودکار ابزار goimports اجرا شود. goimports به طور خودکار import statements را به روزرسانی و فرمت دهی می‌کند، اطمینان حاصل می‌کند که import های بسته درست هستند و import های بی‌استفاده را حذف می‌کند.

//go:embed pattern: این کامنت دستوری یک فرم گسترده‌تر از //go:embed است و به شما اجازه می‌دهد الگویی را برای تطبیق با فایل‌ها یا دایرکتوری‌ها برای جاسازی در حالت تعبیه شده مشخص کنید. این امکانات، در انتخاب فایل‌ها یا دایرکتوری‌های خاص بیشتری ارائه می‌دهد.

//go:nolint: این کامنت دستوری برای سرکوب خطاها و هشدارهای خاص لینتر برای یک خط کد خاص استفاده می‌شود. این اغلب هنگامی استفاده می‌شود که یک قانون لینتر یک false positive را سیگنال می‌دهد یا وقتی دلیل معتبری برای نادیده گرفتن یک مسئله لینتینگ موقتا وجود دارد.

//go:generate go test: این کامنت دستوری برای استفاده در ارتباط با دستور //go:generate استفاده می‌شود تا به صورت خودکار دستور go test اجرا شود. این دستور معمولاً برای تولید و اجرای کد آزمایشی برای یک بسته استفاده می‌شود.

//go:uintptrescapes: این کامنت دستوری برای نشان دادن این است که یک مقدار uintptr ممکن است به حافظه heap فرار کند. به طور پیش فرض، کامپایلر فرض می‌کند که مقدارهای uintptr فرار نمی‌کنند، اما استفاده از این دستور اجازه تجزیه و تحلیل فرار دقیق‌تر را می‌دهد.

//go:build !constraint: این کامنت دستوری برای حذف یک فایل از فرآیند ساخت بر اساس شرط ساخت خاص استفاده می‌شود. این به شما اجازه می‌دهد یک شرط را مشخص کنید که برای شامل شدن فایل در فرآیند ساخت باید برآورده نشود.

//go:checkptr: این کامنت دستوری برای فعال کردن بررسی‌های ایمنی اضافی برای اشاره‌گرها در کد استفاده می‌شود. این دستور به کامپایلر دستور می‌دهد تا بررسی‌های رانتایم اضافی را برای شناسایی عملیات اشاره‌گر ناموفق و مسائل امنیتی حافظه انجام دهد.

//go:nosplitcheck: این کامنت دستوری برای غیرفعال کردن بررسی nosplit برای یک تابع استفاده می‌شود. بررسی nosplit بررسی می‌کند که یک تابع بدون پیش‌بینی از برنامه‌ای که در آینده اجرا می‌شود، بدون توقف توسط برنامه اجرا شود.استفاده از این دستور ممکن است خطرناک باشد و فقط در صورت ضرورت باید استفاده شود.

//go:noruntime: این کامنت دستوری برای نشان دادن این است که یک بسته به Go runtime وابسته نیست. این به کامپایلر اطلاع می‌دهد که بسته می‌تواند در یک محیط استفاده شود که Go runtime در دسترس نیست یا نیاز نیست.

مثال استفاده از directive comment:

 1package main
 2
 3import "fmt"
 4
 5//go:generate stringer -type=Age
 6
 7type Age int
 8
 9const (
10	CHILDERN Age = iota
11	ADOLESCENTS
12	ADULTS
13)
14
15func main() {
16	fmt.Println(CHILDERN.String())
17}

در کد فوق ما یک directive comment اضافه کردیم حال کافیه با استفاده از دستور go generate متد استرینگ تایپ را جنریت کنیم.

1$ go generate ./...