Bu, “Clean Code in Go” seriyasının sonuncusudur. Əvvəlki hissələr: “Clean Code: Functions and Error Handling in Go: From Chaos to Clarity” adlı kitabı təqdim edib. “Clean Code in Go (Part 2): Structs, Methods, and Composition Over Inheritance” (İkinci Bölüm) “Clean Code: Interfaces in Go – Why Small Is Beautiful” (“Çox ki, kiçik də gözəldir”) Clean Code in Go (Part 4): Paket Arsitektri, Bağımlılıq Flow və Scalability Müəllimlər: Niyə konkurentlik çox vacibdir Mən saat 3:00-da goroutin qaçqınlarını düzəltdim, yalnız yük altında görünən yarış koşullarını düzəltdim və yalnız bir eksiklik gördüm. “Memory sharing; memory sharing by communicating” – bu “Go” mantra başının üstündə birbaşa programlaşdırmağa çevrilir. “Mutex” və “Semafor”ların yerində kanallar. “Goroutines” və “Goroutines” kanalları. “Callbacks”ın yerində “Select” seçilir. defer Yarışdığım futbolçuların sayı: Goroutin sızıntısı: -40% istehsal memoriya problemləri Tədbirdə iştirak edənlərin sayı: 35% Konteksti tərk etmək: Timeout buglarının ~50% Kanalı pisləşdirmək üçün Deadlocks: ~25% of hanging services Yanlış mutex istifadə (value receiver): ~30% sync bugs 6 il sonra Go və sistemlərlə işləyən milyonlarca istehlak işlətdikdən sonra, deyim ki, goroutinin və kontekstin düzgün istifadə etməsi, elegant bir çözümlə saat 3:00-da meydana gələn bir istehsal hadisəsi arasındakı fərqdir. Etiket: ömür boyu idarəetmə Birinci kontekst // RULE: context.Context is ALWAYS the first parameter func GetUser(ctx context.Context, userID string) (*User, error) { // correct } func GetUser(userID string, ctx context.Context) (*User, error) { // wrong - violates convention } İstirahət // BAD: operation cannot be cancelled func SlowOperation() (Result, error) { time.Sleep(10 * time.Second) // always waits 10 seconds return Result{}, nil } // GOOD: operation respects context func SlowOperation(ctx context.Context) (Result, error) { select { case <-time.After(10 * time.Second): return Result{}, nil case <-ctx.Done(): return Result{}, ctx.Err() } } // Usage with timeout ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() result, err := SlowOperation(ctx) if err == context.DeadlineExceeded { log.Println("Operation timed out") } Kontekst qiymətləri: Düzgün istifadə edin! // BAD: using context for business logic type key string const userKey key = "user" func WithUser(ctx context.Context, user *User) context.Context { return context.WithValue(ctx, userKey, user) } func GetUser(ctx context.Context) *User { return ctx.Value(userKey).(*User) // panic if no user! } // GOOD: context only for request metadata type contextKey string const ( requestIDKey contextKey = "requestID" traceIDKey contextKey = "traceID" ) func WithRequestID(ctx context.Context, requestID string) context.Context { return context.WithValue(ctx, requestIDKey, requestID) } func GetRequestID(ctx context.Context) string { if id, ok := ctx.Value(requestIDKey).(string); ok { return id } return "" } // BETTER: explicit parameter passing func ProcessOrder(ctx context.Context, user *User, order *Order) error { // user passed explicitly, not through context return nil } Etiket: rahat yarış Şirkət: Working Pool // Worker pool to limit concurrency type WorkerPool struct { workers int jobs chan Job results chan Result wg sync.WaitGroup } type Job struct { ID int Data []byte } type Result struct { JobID int Output []byte Error error } func NewWorkerPool(workers int) *WorkerPool { return &WorkerPool{ workers: workers, jobs: make(chan Job, workers*2), results: make(chan Result, workers*2), } } func (p *WorkerPool) Start(ctx context.Context) { for i := 0; i < p.workers; i++ { p.wg.Add(1) go p.worker(ctx, i) } } func (p *WorkerPool) worker(ctx context.Context, id int) { defer p.wg.Done() for { select { case job, ok := <-p.jobs: if !ok { return } result := p.processJob(job) select { case p.results <- result: case <-ctx.Done(): return } case <-ctx.Done(): return } } } func (p *WorkerPool) processJob(job Job) Result { // Process job output := bytes.ToUpper(job.Data) return Result{ JobID: job.ID, Output: output, } } func (p *WorkerPool) Submit(job Job) { p.jobs <- job } func (p *WorkerPool) Shutdown() { close(p.jobs) p.wg.Wait() close(p.results) } // Usage func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() pool := NewWorkerPool(10) pool.Start(ctx) // Submit jobs for i := 0; i < 100; i++ { pool.Submit(Job{ ID: i, Data: []byte(fmt.Sprintf("job-%d", i)), }) } // Collect results go func() { for result := range pool.results { log.Printf("Result %d: %s", result.JobID, result.Output) } }() // Graceful shutdown pool.Shutdown() } Fan-out və fan-in // Fan-out: distribute work among goroutines func fanOut(ctx context.Context, in <-chan int, workers int) []<-chan int { outputs := make([]<-chan int, workers) for i := 0; i < workers; i++ { output := make(chan int) outputs[i] = output go func() { defer close(output) for { select { case n, ok := <-in: if !ok { return } // Heavy work result := n * n select { case output <- result: case <-ctx.Done(): return } case <-ctx.Done(): return } } }() } return outputs } // Fan-in: collect results from goroutines func fanIn(ctx context.Context, inputs ...<-chan int) <-chan int { output := make(chan int) var wg sync.WaitGroup for _, input := range inputs { wg.Add(1) go func(ch <-chan int) { defer wg.Done() for { select { case n, ok := <-ch: if !ok { return } select { case output <- n: case <-ctx.Done(): return } case <-ctx.Done(): return } } }(input) } go func() { wg.Wait() close(output) }() return output } // Usage func pipeline(ctx context.Context) { // Number generator numbers := make(chan int) go func() { defer close(numbers) for i := 1; i <= 100; i++ { select { case numbers <- i: case <-ctx.Done(): return } } }() // Fan-out to 5 workers workers := fanOut(ctx, numbers, 5) // Fan-in results results := fanIn(ctx, workers...) // Process results for result := range results { fmt.Printf("Result: %d\n", result) } } Kanallar: birinci sınıflar Sürücü kanalları // BAD: bidirectional channel everywhere func producer(ch chan int) { ch <- 42 } func consumer(ch chan int) { value := <-ch } // GOOD: restrict direction func producer(ch chan<- int) { // send-only ch <- 42 } func consumer(ch <-chan int) { // receive-only value := <-ch } // Compiler will check correct usage func main() { ch := make(chan int) go producer(ch) go consumer(ch) } Seçmə və bloklaşdırma prosedurları // Pattern: timeout with select func RequestWithTimeout(url string, timeout time.Duration) ([]byte, error) { result := make(chan []byte, 1) errCh := make(chan error, 1) go func() { resp, err := http.Get(url) if err != nil { errCh <- err return } defer resp.Body.Close() data, err := io.ReadAll(resp.Body) if err != nil { errCh <- err return } result <- data }() select { case data := <-result: return data, nil case err := <-errCh: return nil, err case <-time.After(timeout): return nil, fmt.Errorf("request timeout after %v", timeout) } } // Non-blocking send func TrySend(ch chan<- int, value int) bool { select { case ch <- value: return true default: return false // channel full } } // Non-blocking receive func TryReceive(ch <-chan int) (int, bool) { select { case value := <-ch: return value, true default: return 0, false // channel empty } } Rəsmi vəziyyətlər və onlardan necə qurtulmaq Etiket: Data Race // DANGEROUS: data race type Counter struct { value int } func (c *Counter) Inc() { c.value++ // NOT atomic! } func (c *Counter) Value() int { return c.value // race on read } // Check: go test -race İstehsalçılıq: Mutex type SafeCounter struct { mu sync.RWMutex value int } func (c *SafeCounter) Inc() { c.mu.Lock() defer c.mu.Unlock() c.value++ } func (c *SafeCounter) Value() int { c.mu.RLock() defer c.mu.RUnlock() return c.value } // Pattern: protecting invariants type BankAccount struct { mu sync.Mutex balance decimal.Decimal } func (a *BankAccount) Transfer(to *BankAccount, amount decimal.Decimal) error { // Important: always lock in same order (by ID) // to avoid deadlock if a.ID() < to.ID() { a.mu.Lock() defer a.mu.Unlock() to.mu.Lock() defer to.mu.Unlock() } else { to.mu.Lock() defer to.mu.Unlock() a.mu.Lock() defer a.mu.Unlock() } if a.balance.LessThan(amount) { return ErrInsufficientFunds } a.balance = a.balance.Sub(amount) to.balance = to.balance.Add(amount) return nil } Solution 2: Sincronizasiya üçün kanallar // Use channels instead of mutexes type ChannelCounter struct { ch chan countOp } type countOp struct { delta int resp chan int } func NewChannelCounter() *ChannelCounter { c := &ChannelCounter{ ch: make(chan countOp), } go c.run() return c } func (c *ChannelCounter) run() { value := 0 for op := range c.ch { value += op.delta if op.resp != nil { op.resp <- value } } } func (c *ChannelCounter) Inc() { c.ch <- countOp{delta: 1} } func (c *ChannelCounter) Value() int { resp := make(chan int) c.ch <- countOp{resp: resp} return <-resp } Yarışma modelləri Mövzu: “Graceful Shutdown” type Server struct { server *http.Server shutdown chan struct{} done chan struct{} } func NewServer(addr string) *Server { return &Server{ server: &http.Server{ Addr: addr, }, shutdown: make(chan struct{}), done: make(chan struct{}), } } func (s *Server) Start() { go func() { defer close(s.done) if err := s.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Printf("Server error: %v", err) } }() // Wait for shutdown signal go func() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) select { case <-sigCh: case <-s.shutdown: } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := s.server.Shutdown(ctx); err != nil { log.Printf("Shutdown error: %v", err) } }() } func (s *Server) Stop() { close(s.shutdown) <-s.done } Etiket: limitlər type RateLimiter struct { rate int bucket chan struct{} stop chan struct{} } func NewRateLimiter(rate int) *RateLimiter { rl := &RateLimiter{ rate: rate, bucket: make(chan struct{}, rate), stop: make(chan struct{}), } // Fill bucket for i := 0; i < rate; i++ { rl.bucket <- struct{}{} } // Refill bucket at given rate go func() { ticker := time.NewTicker(time.Second / time.Duration(rate)) defer ticker.Stop() for { select { case <-ticker.C: select { case rl.bucket <- struct{}{}: default: // bucket full } case <-rl.stop: return } } }() return rl } func (rl *RateLimiter) Allow() bool { select { case <-rl.bucket: return true default: return false } } func (rl *RateLimiter) Wait(ctx context.Context) error { select { case <-rl.bucket: return nil case <-ctx.Done(): return ctx.Err() } } Tədbirlər:Pipeline with error handling // Pipeline stage with error handling type Stage func(context.Context, <-chan int) (<-chan int, <-chan error) // Compose stages func Pipeline(ctx context.Context, stages ...Stage) (<-chan int, <-chan error) { var ( dataOut = make(chan int) errOut = make(chan error) dataIn <-chan int errIn <-chan error ) // Start generator start := make(chan int) go func() { defer close(start) for i := 1; i <= 100; i++ { select { case start <- i: case <-ctx.Done(): return } } }() dataIn = start // Apply stages for _, stage := range stages { dataIn, errIn = stage(ctx, dataIn) // Collect errors go func(errors <-chan error) { for err := range errors { select { case errOut <- err: case <-ctx.Done(): return } } }(errIn) } // Final output go func() { defer close(dataOut) for val := range dataIn { select { case dataOut <- val: case <-ctx.Done(): return } } }() return dataOut, errOut } Təsadüfi tips Uzun işlər üçün hər zaman kontekst istifadə edin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”nin “Qəbələ”in “Qəbələ”in “Qəbələ”in “Qəbələ”in “Qəbələ”in “Qəbələ”nə” Koordinasiya üçün kanalları müteksisə tercih edin “Sync/atomic” və “Simple Counter” “Race Flag” testləri Kanalı kəşf etmək Düşünürəm ki, hər zaman “Graceful Shutdown” Yarışa checklist Birinci parametri ilə bağlı Kontekstdən istifadə edilə bilər Xatırladaq ki, heç bir problem yoxdur. Sender tərəfindən qapılan kanallar Mütəxəssislər bu sistemin bir sıra Testlər -race bayrağı ilə keçib Qızıl qapalı qapalı Bulk operasiyaları üçün işçi pul Sonrakı Movqe.Az xəbər verir ki, Hollandiyada, erkən parlament seçkilərin nəticəsi çox az fərqlə iqtidardakı Liberal Partiyasının (VVD) qazandığını göstərir. Bu yazı "Clean Code in Go" seriyasını bitirir. Biz funksiyadan bir-birinə gələrək, idiomatik Go kodunu yazmaqın bütün əsas aspektlərini əhatə etdik. Unutmayın: Go simvoliyadır, və Go-da təmiz kodu dili idiomlarını izləyən koddur. Xatırladaq ki, ən böyük problemləriniz nədir? - Xatırladaq ki, ən böyük problemləriniz nədir? - Xatırladaq ki, daha çox problemlər var.