CloudyGo
212 subscribers
8 photos
1 video
1 file
41 links
Software Engineering & Development Best Practices, Especially in Golang!


https://cloudygo.ir

@rezakhademix
Download Telegram
#گولنگ

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


نمونه کد بد:
type User struct {
sync.Mutex

data map[string]string
}

func NewUser() User {
return User{
data: make(map[string]string),
}
}

func (m User) Get(k string) string {
m.Lock()
defer m.Unlock()

return m.data[k]
}

نمونه کد خوب:
type User struct {
mu sync.Mutex

data map[string]string
}

func NewUser() *User {
return &User{
data: make(map[string]string),
}
}

func (m *User) Get(k string) string {
m.mu.Lock()
defer m.mu.Unlock()

return m.data[k]
}

#tip
#golang
👍2🔥1
#گولنگ

معمولا گولنگ دولوپرها تفاوت و کاربرد اسلایس‌های empty و nil رو باهم اشتباه میگیرن.
به کد زیر دقت کنید:

‍‍
// #1
var s []string

// #2
s = []string(nil)

// #3
s = []string{}

// #4
s = make([]string, 0)

کافیه برای اسلایس‌های بالا کد زیر رو اجرا کنید تا تفاوت nil و empty بودن رو متوجه بشین:

‍‍
// In Golang "\t" means tab character "\n" means new line

fmt.Printf("IsEmpty=%v \t IsNil=%v \n", len(s) == 0, s == nil)

نتیجه میشه:

#1
IsEmpty=true IsNil=true

#2
IsEmpty=true IsNil=true

#3
IsEmpty=true IsNil=false

#4
IsEmpty=true IsNil=false


خب هرچهار مورد به‌عنوان empty در نظر گرفته میشن یعنی اگر len اونارو بگیرین برابر با صفر میشه اما فقط دو مورد اول nil هستن.

دقت کنید:
۱) اسلایس‌های nil در واقع empty هم هستن
۲) اسلایس‌های nil در حافظه allocation ایجاد نمی‌کنن
۳) برای هردو نوع اسلایس‌های nil و empty میتونیم از تابع append استفاده کنیم


خب پس کاربرد اسلایس nil چیه؟
فرض کنید تابعی دارین که داره یک یا چند اسلایس برمیگردونه، خب دیگه با گولنگ نیازی نیست که مثل زبان‌های دیگه یک مقدار allocate شده برگردونید و با برگردوندن nil یا یک اسلایس nil حافظه کمتری مصرف میشه.

func scores() []int {
s, err := apiCall();
if err != nil {
return nil
}

return s
}



#golang
#tip
👏2
#گولنگ

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

گوروتین‌ها در گولنگ توسط grabage collector پاکسازی نمیشن و با وجود سبک بودنشون هنوز هم میتونن باعث ایجاد مموری لیک (memory leak) در برنامه‌تون بشن یا برای همیشه در پس‌زمینه در حال اجرا باقی بمونن!

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

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

#golang
#tip
👍2
#گولنگ

یکی از توصیه‌های کاربردی و مهم در تابع main برنامه‌های گولنگی اینه که بهتره نهایتا یک‌بار از log.Fatal یا os.Exit استفاده کنیم.

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

کد معمولی:

package main

func main() {
args := os.Args[1:]
if len(args) != 1 {
log.Fatal("file is missing")
}
name := args[0]

f, err := os.Open(name)
if err != nil {
log.Fatal(err)
}
defer f.Close()

// If we call log.Fatal after this line,
// f.Close will not be called.

b, err := io.ReadAll(f)
if err != nil {
log.Fatal(err)
}

db, err := db.New()
if err != nil {
log.Fatal(err)
}
defer db.Close()

// Again, if we call log.Fatal after
// this line, db.Close will not be called.

// ...
}

کد بهتر:

package main

func main() {
if err := run(); err != nil {
log.Fatal(err)
}
}

func run() error {
args := os.Args[1:]
if len(args) != 1 {
return errors.New("file is missing")
}
name := args[0]

f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close()

b, err := io.ReadAll(f)
if err != nil {
return err
}

db, err := db.New()
if err != nil {
return err
}
defer db.close()

// ...
}

#golang
#tip
👍1
نمونه کد اجرای یک کانکشن SSE در گولنگ:


import (
"fmt"
"net/http"
"time"
)

func sseHandler(w http.ResponseWriter, r *http.Request) {
// set SSE http headers
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")

// create a channel for client disconnection
clientIsGone := r.Context().Done()

rc := http.NewResponseController(w)

t := time.NewTicker(time.Second)
defer t.Stop()

for {
select {
case <-clientIsGone:
fmt.Println("client disconnected")
return

case <-t.C:
// send event to the client
_, err := fmt.Fprintf(w, "data: the time is %s\n\n", time.Now().Format(time.UnixDate))
if err != nil {
return
}

err = rc.Flush()
if err != nil {
return
}
}
}
}


#tip
#golang
#golang
#tip

مشکل کد زیر کجاست؟

‍‍‍
func main() {

names := make([]string, 0, 10)

names[0] = "reza"

fmt.Println(names)
}

خب این برنامه در ظاهر مشکلی نداره، یک اسلایس تعریف و در خط بعدی ایندکس صفرم دارای مقدار شده، اما نکته مهم اینجاست برای متغیر names یک اسلایس با ظرفیت (capacity) 10، اما طول (length) حقیقی صفر ایجاد شده و هنگامی ‌که در خط بعدی تصمیم به مقداردهی یکی از ایندکس‌های اسلایس می‌کنیم برنامه با خطای ران‌تایم مواجه میشه و نتیجه چنین کدی مساوی با خطای زیر است:

‍‍‍
panic: runtime error: index out of range [0]  with length 0


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

names = append(names, "reza")


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

 names := make([]string, 10)


در این نوع تعریف علاوه‌بر تعیین ظرفیت، طول حقیقی اسلایس نیز معین میشه تا از خطای ران‌تایم جلوگیری شود.
دقت کنید تعریف اسلایس با کلمه کلیدی var و تکرار روند بالا بازهم برنامه را با خطای index out of range روبرو خواهد کرد.

var ages []int
ages[1] = 29 // this line will fail with runtime error: index out of range [1] with length 0



#tip
#golang
1🔥1😱1
گولنگ ۱.۲۴ در ماه فوریه ۲۰۲۵ ارائه میشه و بالاخره توسعه‌هندگان تصمیم گرفتن تگ omitzero رو برای پکیج encoding/json فعال کنن و از این به بعد با نوشتن این تگ علاوه بر مقادیر پیش‌فرض، استراکچرهای خالی هم حذف میشن!

در ورژن‌های فعلی گولنگ تگ‌ omitempty رو داشتیم اما تنها فیلدهای با مقادیر پیش فرض رو حذف میکرد.

یه نگاه به نمونه کد زیر بندازین تا بیشتر موضوع روشن بشه:

type Blog struct {
Name string `json:"omitempty"`
Tags []string `json:"omitempty"`
Posts Posts `json:"omitempty"`
}

تگ‌ omitempty استراکچر درونی Posts رو حذف نمیکنه و فقط فیلدهای Name و Tags رو به شرطی حذف می‌کنه که مقدار پیش‌فرض گولنگی خودشون رو داشته باشن.

برای اینکه این مقادیر رو حذف کنید باید اینترفیس marshaler رو خودتون برای استراکت پیاده‌سازی کنید اما با ورژن ۱.۲۴ با تگ omitzero استراکت‌های خالی به شکل خودکار حذف میشن.

type Blog struct {
Name string `json:"omitzero"`
Tags []string `json:"omitzero"`
Posts Posts `json:"omitzero"`
}


لینک ریلیز ۱.۲۴ گولنگ برای جزییات بیشتر:
https://tip.golang.org/doc/go1.24


#tip
#golang
👏5
اگر در اپلیکیشنی که دارید زیاد از time.Time استفاده می‌کنید می‌تونید با استفاده از ایجاد یک تایپ دلخواه و استفاده int64 میزان مصرف مموری رو تا حد قابل قبولی کاهش بدید و پرفورمنس رو بهتر کنید. به مثال زیر دقت کنید:

‍‍‍‍
type Timestamp int64

func (t Timestamp) Time() time.Time {
return time.Unix(int64(t), 0)
}

#tip
#golang
👍3
This media is not supported in your browser
VIEW IN TELEGRAM
پکیج go-tmpl بهتون این امکان رو میده خیلی ساده کدهای گولنگ رو با HTML ترکیب کنید. هرچند که خود استانداد لایبرری گولنگ از این موضوع پشتیبانی می‌کنه اما خب این پکیج امکانات جالبی داره که در ویدیو بالا می‌تونید ببینید.

https://github.com/a-h/templ

#golang
#packge
👍6
تمام تغییرات زبان گولنگ در نسخه 1.24 رو در لینک زیر مشاهده و اجرا کنید!

لینک زیر یک محیط تعاملی برای درک بهتر قابلیت‌های Golang در ورژن 1.24 است.

https://antonz.org/go-1-24/


#golang
#tip
👏21
نوشتن middleware برای اجرا مجموعه‌ای پروسه‌ها قبل از رسیدن درخواست به هندلر برای gRPC سرور کار دشواری نیست.

اما خب اگر تمایل دارین پکیجی استفاده کنین که خودش خودکار لاگ بزنه و ردیابی رو‌ با استانداردهای Otel در اختیارتون قرار بده و‌ مواردی
مثل:
Auth,
Metrics (Prometheus),
Logging,
Selector,
Validator,
Ratelimit

و غیره رو خیلی راحت‌تر داشته باشین، یه نگاهی به پکیج زیر بندازین.

نمونه کد:

 grpcSrv := grpc.NewServer(
grpc.StatsHandler(otelgrpc.NewServerHandler()),
grpc.ChainUnaryInterceptor(
srvMetrics.UnaryServerInterceptor(grpcprom.WithExemplarFromContext(exemplarFromContext)),
logging.UnaryServerInterceptor(interceptorLogger(rpcLogger), logging.WithFieldsFromContext(logTraceID)),
selector.UnaryServerInterceptor(auth.UnaryServerInterceptor(authFn), selector.MatchFunc(allButHealthZ)),
recovery.UnaryServerInterceptor(recovery.WithRecoveryHandler(grpcPanicRecoveryHandler)),
),
)

https://github.com/grpc-ecosystem/go-grpc-middleware



#package
#golang
👍3🙏1
اگر دوست‌دارین best practiceها و نکات ریزی که حین استفاده از defer در گولنگ بهش برخورد می‌کنید رو بدونید و از اشتباهات رایج جلوگیری کنید، مقاله زیر بهتون کمک می‌کنه.


https://rezakhademix.medium.com/defer-functions-in-golang-common-mistakes-and-best-practices-96eacdb551f0



#tip
#golang
👍4🙏1
مقاله زیر با زبان خیلی ساده به ما میگه که چطور از اشتباهات پیش‌پا افتاده جلوگیری کنیم و بتونیم اپلیکیشن گولنگی که داریم به برای تعداد رکوئست بالا آماده کنیم.


https://dev.to/rikenshah/scaling-backend-to-1m-requests-with-just-2gb-ram-4m0c


#tip
#golang
👍4🔥1
سوال #استخدامی:

اگر فانکشن modify رو به شکل زیر تعریف و سپس تابع main رو با بدنه نوشته اجرا کنیم، در نتیجه کدام یک از گزینه‌ها چاپ خواهد شد؟

1. Compilation Error
2. [100, 200, 300, 4]
3. [1, 2, 3, 4]
4. [100, 200, 300, 0]


func modify(s []int){
copy(s, []int{100, 200, 300})
}

func main(){
nums := []int{1, 2, 3, 4}
modify(nums)
fmt.Println(nums)
}



در زبان Go، تابع copy(dest, src) عناصر src را در dest کپی می‌کند، اما اندازه dest را تغییر نمی‌دهد.

در اینجا، دستور
 copy(nums, []int{100, 200, 300})

سه عنصر اول آرایه nums را جایگزین می‌کند، اما طول اصلی آن را تغییر نمی‌دهد.

بنابراین، وقتی fmt.Println(nums) را اجرا کنیم، خروجی
 [100, 200, 300, 4] 

خواهد بود. (گزینه دو)



#golang
#qa
👍7👏2
اگر دوست‌دارین best practiceها و نکات ریزی که حین استفاده از sliceها در گولنگ بهش برخورد می‌کنید رو بدونید و از اشتباهات رایج جلوگیری کنید، مقاله زیر بهتون کمک می‌کنه.


https://rezakhademix.medium.com/slices-in-golang-common-mistakes-and-best-practices-76c30857d4e4


#tip
#golang
👍4👨‍💻1
اگر دوست‌دارین best practiceها و نکات ریزی که حین استفاده از stringها در گولنگ بهش برخورد می‌کنید رو بدونید و از اشتباهات رایج جلوگیری کنید، مقاله زیر بهتون کمک می‌کنه.


https://rezakhademix.medium.com/strings-in-golang-common-mistakes-and-best-practices-1250045051f8

#tip
#golang
2👍2
زمانی که با گولنگ توسعه میدیم باید دقت خوبی در استفاده از گوروتین‌ها، کنترل پوینترها و موضوعات دیگر با هدف جلوگیری از مموری‌لیک داشته باشیم.

ابزارهای مانیتورینگ و پروفایل کردن برنامه گولنگی مثل: pprof و Prometheus و... هم کمک خوبی برای تشخیص بهتر اشتباهات توسعه و مموری‌لیک هستند.


اما سورپرایز ویژه اضافه شدن دستور

go build -asan 

هستش که قرار هست به گولنگ ۱.۲۵ اضافه شود و با دقت خوبی، مموری‌لیک‌های برنامه رو در هنگام خروج مشخص نماید.


#golang
👍9🔥31
بعد از مدت‌ها بالاخره Gorm آپدیتی که اکثر برنامه‌نویسان گولنگ منتظرش بودند رو در نسخه جدید منتشر کرد.

در کامیونیتی گولنگ Gorm بخاطر عدم Type Safty و پیچیدگی‌های مختلف آنچنان محبوب نیست. مدت‌ها بود که Generic ها به زبان گولنگ اضافه شدن اما با وجود درخواست‌های زیاد، همچنان در Gorm مورد استفاده قرار نگرفته بودند تا اینکه در ورژن جدید Gorm یعنی V1.30.0 بالاخره این قابلیت به Gorm اضافه شده و میتونه برگ برنده‌ای برای این ORM باشه.

شخصا از Gorm بخاطر عدم بهینه بودن، رفتار عجیب و… استفاده نمی‌کردم اما این آپدیت آنقدر جدی هست که در صفحه اول داکیومنت Gorm خیلی شفاف به این قابلیت پرداخته و تاکید کرده فقط در ورژن‌های V1.30 به بالا قابل استفاده است. شخصا مایلم یک بار دیگه تستش کنم و ببینم تونسته مشکلات متعددی که داشت رو رفع کنه یا خیر!

به نظرم به‌زودی این سینتکس جدید، به روش پیش‌فرض کوئری نوشتن در Gorm تبدیل میشه!


#gorm
#golang
@cloudygo
2👍2👏2