4.18 تکنیک های کدنویسی زبان گو

4.18 تکنیک های کدنویسی زبان گو

در این بخش به موارد زیر می پردازیم.

  • کارآمدی (effective)
  • استایل اوبر
  • تکنیک ها
  • نکات فنی
  • بهینه سازی

4.18.1 کارآمدی (Effective) #

در زیر به کارآمدی زبان گو می پردازیم.

4.18.1.1 نام پکیج ها #

زمانیکه شما یک پکیج را ایمپورت میکنید نام پکیج در بالای بدنه فایل گو قرار میگیرد مانند :

1import "bytes"

حال وقتی که پکیج ایمپورت می شود و نام اش در بالای بدنه فایل کد شما قرار میگیرد می توانید در ادامه نام پکیج توابع, تایپ ها و یا متغیر و const ها را مانند bytes.Buffer فراخوانی کنید.

انتخاب یک نام خوب برای پکیج خیلی قابل اهمیت است و شما برای انتخاب یک نام خوب بهتر است موارد زیر را رعایت کنید :

  • نام کوتاه باشد.
  • مختصر باشد.
  • نام پکیج طوری باشد که به آسانی بتوان بهش دسترسی داشت
  • نام پکیج باشد تماما حروف کوچک باشد.
  • تک کلمه ای باشد.

4.18.1.2 پیاده سازی Getter/Setter #

در زبان گو هیچ Getter یا Setter خودکاری وجود ندارد. به همین منظور شما باید Getter/Setter را در قالب متد یک آبجکت پیاده سازی کنید.

1owner := obj.GetOwner()
2if owner != user {
3    obj.SetOwner(user)
4}

4.18.1.3 نام اینترفیس #

نام اینترفیس یک حالت قراردادی دارد که بهتر است این حالت قرار دادی را رعایت کنید. نام اینترفیس با مشخصه یک رفتار کلی باشد که برپایه متدهای اینترفیس تعیین می شود و در نهایت در انتهای نام اینترفیس دو حرف er اضافه می شود. مانند : Reader , Writer, Formmater

1type Writer interface {
2	Write([]byte) error
3}

4.18.1.4 نوع نام گذاری متغیر, تابع, تایپ و … #

در زبان گو نام گذاری حالت قراردادی دارد که کامپایلر نسبت به نوع نام گذاری شما رفتار نشان می دهد. بطوری که شما می توانید نام ها را بصورت MixedCaps یا mixedCaps بنویسید که این حالت نام گذاری را camleCase و PascalCase می گویند.

  • زمانیکه شما نام را بصورت PascalCase می نویسید در واقع حالت شما آن متغیر, تابع, تایپ و … را بصورت Public در نظر گرفتید.
  • اگر شما نام را بصورت camleCase بنویسید در واقع شما متغیر, تابع, تایپ و … را بصورت Private در نظر گرفتید وفقط در پکیج لول شما در دسترس می باشد.

4.18.1.5 نقطه ویرگول (Semicolons) #

مانند C، گرامر رسمی Go از نقطه ویرگول برای پایان دادن به عبارات استفاده می کند، اما برخلاف C، این نقطه ویرگول ها در منبع ظاهر نمی شوند. در عوض، lexer از یک قانون ساده برای درج خودکار نقطه ویرگول ها در حین اسکن استفاده می کند، بنابراین متن ورودی عمدتاً فاقد آنها است.

4.18.1.6 ساختارهای کنترلی if, for, switch #

در زبان گو همانند سایر زبان ها ساختارهای کنترلی نظیر if, for, switch داریم که در زیر می توانید با حالت های کارآمد استفاده از این کنترل ها آن ها آشنا شوید.

if

در زبان گو حالت ساده شرط به شکل زیر است :

1if x > 0 {
2    return y
3}

حال اگر شما یک تابعی داشته باشید که یک مقدار مانند خطا برگرداند می توانید داخل عبارت شرط یک متغییر راه اندازی کنید و تابع را داخلش قرار دهید سپس با قرار دادن نقطه ویرگول شرط را بررسی کرده.

1if err := file.Chmod(0664); err != nil {
2    log.Print(err)
3    return err
4}

اما اگر تابع شما ۲ تا خروجی داشته باشد بهتر است داخل دو متغییر خروجی را بگیرید و در خط بعدی شرط را جهت بررسی هریک از متغیرها قرار دهید:

1f, err := os.Open(name)
2if err != nil {
3    return err
4}
5codeUsing(f)

4.18.2 استایل اوبر (Uber) #

در زیر استایل کدنویسی که تیم مهندسی شرکت اوبر تهیه کردند می پردازیم.

4.18.3 تکنیک ها #

در زیر به تکنیک های زبان گو می پردازیم.

4.18.4 نکات فنی #

در زیر چندین نکات فنی قرار دادم که کاربردی می باشد.

4.18.4.1 مقدار صفر تایپ ها و مقادیر #

همانطور در فصل های قبل اشاره کردیم تایپ ساختار (struct) بدون فیلد مقدارش در حافظه کاملا صفر است. اندازه تایپ آرایه بدون هیچ المنتی صفر است. حال در زیر یک مثال میزنیم تا ببینید:

 1package main
 2
 3import "unsafe"
 4
 5type A [0][256]int
 6type S struct {
 7	x A
 8	y [1 << 30]A
 9	z [1 << 30]struct{}
10}
11type T [1 << 30]S
12
13func main() {
14	var a A
15	var s S
16	var t T
17	println(unsafe.Sizeof(a)) // 0
18	println(unsafe.Sizeof(s)) // 0
19	println(unsafe.Sizeof(t)) // 0
20}
1$ go run main.go
20
30
40

در Go، اندازه ها اغلب به عنوان مقادیر int نشان داده می شوند. این به این معنی است که بزرگترین طول ممکن یک آرایه MaxInt است که مقدار آن در سیستم عامل های 64 بیتی 2^63-1 است. با این حال، طول آرایه با اندازه عناصر غیر صفر به سختی توسط کامپایلر استاندارد رسمی Go و زمان اجرا محدود می شود.

1var x [1<<63 - 1]struct{} // okay
2var y [2000000000 + 1]byte // compilation error
3var z = make([]byte, 1<<49) // panic: runtime error: makeslice: len out of range

4.18.4.2 نحوه تخصیص مقادیر اندازه صفر به کامپایلر بستگی دارد #

در اجرای استاندارد رسمی فعلی کامپایلر Go (نسخه 1.20)، همه مقادیر محلی صفر تخصیص داده شده روی heap و آدرس یکسانی دارند. به عنوان مثال، موارد زیر دو بار false را چاپ می کنند، سپس دو بار true را چاپ می کنند.

 1package main
 2
 3var g *[0]int
 4var a, b [0]int
 5
 6//go:noinline
 7func f() *[0]int {
 8	return new([0]int)
 9}
10func main() {
11	// x and y are allocated on stack.
12	var x, y, z, w [0]int
13	// Make z and w escape to heap.
14	g = &z
15	g = &w
16	println(&b == &a)  // false
17	println(&x == &y)  // false
18	println(&z == &w)  // true
19	println(&z == f()) // true
20}
1$ go run main.go
2false
3false
4true
5true
لطفا توجه داشته باشید که خروجی های برنامه فوق به کامپایلرهای خاصی بستگی دارد. خروجی ها ممکن است برای نسخه های کامپایلر استاندارد رسمی Go در آینده متفاوت باشند.

4.18.4.3 فیلد با اندازه صفر را به عنوان فیلد نهایی یک نوع ساختار قرار ندهید #

در کد زیر اندازه تایپ Tz از تایپ Ty بزرگتر است.

 1package main
 2
 3import "unsafe"
 4
 5type Ty struct {
 6	_ [0]func()
 7	y int64
 8}
 9type Tz struct {
10	z int64
11	_ [0]func()
12}
13
14func main() {
15	var y Ty
16	var z Tz
17	println(unsafe.Sizeof(y)) // 8
18	println(unsafe.Sizeof(z)) // 16
19}
1$ go run main.go
28
316

چرا اندازه نوع Tz بیشتر است؟

در پیاده‌سازی runtime Go استاندارد کنونی، تا زمانی که یک بلوک حافظه توسط حداقل یک اشاره‌گر زنده مشارکت شود، آن بلوک حافظه به عنوان زباله در نظر گرفته نمی‌شود و جمع‌آوری نمی‌شود. همه فیلدهای یک مقدار ساختار قابل دسترسی می‌توانند آدرس‌گرفته شوند. اگر اندازه فیلد نهایی در یک مقدار ساختار با اندازه غیر صفر صفر باشد، آنگاه گرفتن آدرس فیلد نهایی در مقدار ساختاری، آدرسی را که خارج از بلوک حافظه اختصاص داده شده برای مقدار ساختاری است باز خواهد گرداند. آدرس بازگردانده شده ممکن است به بلوک حافظه دیگری که به طور نزدیکی پس از بلوک حافظه اختصاص داده شده برای مقدار ساختاری با اندازه غیر صفر قرار دارد، اشاره کند. تا زمانی که آدرس بازگردانده شده در یک مقدار اشاره‌گر زنده ذخیره شود، بلوک حافظه دیگری که به جمع‌آوری زباله می‌روید جمع‌آوری نخواهد شد که ممکن است باعث نشت حافظه شود. برای جلوگیری از این نوع مشکلات نشت حافظه، کامپایلر Go استاندارد تضمین می‌کند که دریافت آدرس فیلد نهایی در یک ساختار با اندازه غیر صفر هرگز آدرسی را که خارج از بلوک حافظه اختصاص داده شده برای ساختار نیست را بازنخواهد گرداند. کامپایلر Go استاندارد این کار را با وارد کردن برخی بایت‌ها پس از فیلد صفر آخرین انجام می‌دهد. بنابراین، حداقل یک بایت پس از فیلد نهایی (صفر) نوع Tz وجود دارد. به همین دلیل اندازه نوع Tz بزرگتر از Ty است. در واقع، در سیستم عامل‌های ۶۴ بیتی، ۸ بایت پس از فیلد نهایی (صفر) Tz وجود دارد. برای توضیح این موضوع، باید دو حقیقت را در پیاده‌سازی کامپایلر استاندارد رسمی بدانیم:

  1. تضمین ترازبندی یک نوع ساختاری، بزرگترین تضمین ترازبندی فیلدهای آن است.
  2. اندازه یک نوع همیشه یک ضریبی از تضمین ترازبندی آن است. حقیقت اول، علت برابری تضمین ترازبندی نوع Tz با ۸ (که تضمین ترازبندی نوع int64 است) را توضیح می‌دهد. حقیقت دوم، علت برابری اندازه نوع Tz با ۱۶ را توضیح می‌دهد.

منبع : https://github.com/golang/go/issues/9401

4.18.5 بهینه سازی #

در زیر به بهینه سازی در زبان گو می پردازیم.