Idiomatic Go is code written in the way the Go community accepts and prefers. Go has strong design philosophy and clear guidance on how to write code. Focus on clarity, simplicity, and readability. Idiomatic Go code will be easy for others to understand and maintain.
Idiomatic Go là mã viết theo cách được cộng đồng Go chấp nhận và ưa thích. Go có triết học thiết kế mạnh mẽ và hướng dẫn rõ ràng về cách viết code. Tập trung vào sự rõ ràng, tính đơn giản và khả năng đọc hiểu. Mã idiomatic Go dễ dàng sẽ dễ dàng cho những người khác hiểu và bảo trì.
Rob Pike, one of Go's creators, wrote Go proverbs that guide the design philosophy. These proverbs distill the wisdom of the Go community and should guide your code design.
Rob Pike, tác giả Go, đã viết các ngạn ngữ hướng dẫn triết học thiết kế của Go. Những ngạn ngữ này tóm tắt sự khôn ngoan của cộng đồng Go và nên hướng dẫn thiết kế code của bạn.
Proverb 1: Don't communicate by sharing memory; share memory by communicating
Ngạn ngữ 1: Đừng giao tiếp bằng cách chia sẻ bộ nhớ; chia sẻ bộ nhớ bằng cách giao tiếp
Instead of using shared memory with mutexes, use channels to pass data between goroutines. Channels force you to think about data ownership and when it changes hands.
Thay vì dùng shared memory với mutex, hãy dùng channel để truyền dữ liệu giữa các goroutine. Channel buộc bạn suy nghĩ về sở hữu dữ liệu và khi nào nó được chuyển.
1// Bad: shared memory with mutex2var counter int3var mu sync.Mutex45func increment() {6 mu.Lock()7 counter++8 mu.Unlock()9}1011// Good: use channels12func incrementWithChannels() {13 ch := make(chan int)14 go func() {15 ch <- 116 }()17 value := <-ch // receive ownership of value18}Concurrency is the ability to handle multiple tasks by switching between them. Parallelism is running multiple tasks simultaneously on multiple CPUs. Go supports concurrency well with goroutines, but parallelism depends on the number of CPUs.
Concurrency là khả năng xử lý nhiều tác vụ bằng cách giao phó giữa chúng. Parallelism là chạy nhiều tác vụ cùng lúc trên nhiều CPU. Go hỗ trợ concurrency tốt với goroutine, nhưng parallelism phụ thuộc vào số CPU.
1// Concurrent: multiple goroutines but may run on 1 CPU2go func() { doTask1() }()3go func() { doTask2() }()4go func() { doTask3() }()56// Parallelism requires multiple CPUs7// runtime.GOMAXPROCS(runtime.NumCPU()) // use all CPUsProverb 3: The bigger the interface, the weaker the abstraction
Ngạn ngữ 3: Interfaces lớn hơn là trừu tượng yếu hơn
Small, focused interfaces are good. They enable decoupling and testability. Large interfaces usually have few implementations and are hard to use. The most popular Go interfaces (io.Reader, io.Writer) have just one method.
Interface nhỏ, tập trung là tốt. Chúng giúp giải ngôn độc lập và khả năng kiểm tra. Interface lớn thường có ít implementation và khó sử dụng. Các interface Go phổ biến nhất (io.Reader, io.Writer) chỉ có 1 phương thức.
1// Bad: interface lớn2type DataStore interface {3 Get(key string) (interface{}, error)4 Set(key string, value interface{}) error5 Delete(key string) error6 Update(key string, value interface{}) error7 Exists(key string) (bool, error)8 GetAll() (map[string]interface{}, error)9 Clear() error10 // Hard to implement, hard to mock11}12Types should be useful even when initialized with zero values (0, false, nil, empty string). Example: bytes.Buffer works immediately, sync.Mutex is useful without initialization. This reduces opportunities for errors.
Kiểu nên có ý nghĩa ngay cả khi được khởi tạo với zero value (0, false, nil, chuỗi rỗng). Ví dụ: bytes.Buffer hoạt động ngay lập tức, sync.Mutex hữu ích mà không cần khởi tạo. Điều này giảm cơ hội lỗi.
1// Good: bytes.Buffer is useful immediately2var buf bytes.Buffer3buf.WriteString("Hello") // works without explicit initialization4fmt.Println(buf.String()) // "Hello"56// Good: sync.Mutex is useful immediately7var mu sync.Mutex8mu.Lock() // safe, no nil dereference9mu.Unlock()1011// Bad: requires explicit initialization12type Config struct {Proverb 5: A little copying is better than a little dependency
Ngạn ngữ 5: Một chút sao chép tốt hơn một chút phụ thuộc
Sometimes, copying a few lines of code is better than adding a package dependency. Dependencies have costs: maintenance, compatibility, security risk. If the code is small and self-contained, copying is reasonable.
Thỉnh thoảng, sao chép một vài dòng code tốt hơn thêm một package phụ thuộc. Phụ thuộc có chi phí: bảo trì, độc lập, rủi ro bảo mật. Nếu code nhỏ và riêng biệt, sao chép nó là hợp lý.
Write clear, understandable code, even if it is longer. Clever tricks are not always good -> they make it hard for others to read. Go favors clarity over elegance.
Viết code rõ ràng, dễ hiểu, thậm chí nếu nó dài hơn. Kỹ năng clever (những trick thông minh) không phải lúc nào cũng tốt — nó làm khó khăn cho những người khác đọc code. Go có ưa thích sự rõ ràng hơn sự thanh lịch.
1// Clever but hard to read2result := func(s []string) string {3 return strings.Join(append(make([]string, 0, len(s)), s...), ",")4}(data)56// Clear and straightforward7result := strings.Join(data, ",")Handle errors like any other value. You can store, pass, and check errors. There are no exceptions -> errors are returned and handled.
Xử lý lỗi giống như bất kỳ giá trị khác. Bạn có thể lưu trữ, truyền, kiểm tra lỗi. Không có ngoại lệ — lỗi được trả về và xử lý.
1// Good: errors as values2if err := doSomething(); err != nil {3 // handle error4 return fmt.Errorf("failed: %w", err)5}67// Good: create error values8var ErrNotFound = errors.New("not found")9var ErrInvalid = errors.New("invalid")1011// Check specific errors12if errors.Is(err, ErrNotFound) {13 // handle not found14}Proverb 8: Don't just check errors, handle them gracefully
Ngạn ngữ 8: Đừng chỉ kiểm tra error, xử lý nó một cách tử tế
Do not just acknowledge errors -> provide useful context, rollback transactions, clean up resources. Error handling is important application logic.
Không chỉ nhận ra lỗi — cung cấp bối cảnh hữu ích, rollback transactions, giải phóng resources. Xử lý lỗi là phần quan trọng của logic ứng dụng.
Functions should accept interfaces (allowing flexibility) but return concrete structs (so callers don't depend on abstractions). This creates powerful and testable APIs.
Hàm nên chấp nhận interface (cho phép tính linh hoạt) nhưng trả về struct cụ thể (giúp người gọi không cần phụ thuộc trừu tượng). Điều này tạo ra API mạnh mẽ và dễ kiểm tra.
1// Accept interface (flexible)2func LoadConfig(r io.Reader) (*Config, error) {3 data, err := io.ReadAll(r)4 if err != nil {5 return nil, err6 }7 var cfg Config8 json.Unmarshal(data, &cfg)9 return &cfg, nil10}1112// Caller can pass file, network connection, buffer, etc.Go has a convention: short names for short scope. Loop variables, function parameters, return values should have short names. Long names are for globals and functions.
Go có quy ước: tên ngắn cho phạm vi ngắn. Biến vòng lặp, tham số hàm, giá trị trả về nên có tên ngắn. Tên dài được sử dụng cho biến toàn cầu và hàm.
1// Good: short names for loop variables2for i := 0; i < len(items); i++ {3 fmt.Println(items[i])4}56for _, v := range items {7 process(v)8}910for key, val := range m {11 fmt.Printf("%s: %v\n", key, val)12}Goroutines have overhead. Do not create a goroutine for every task. Start simple, then optimize based on profiling. Often a simple function on the main thread works fine.
Goroutine có overhead. Đừng tạo một goroutine cho mỗi tác vụ. Hãy hẹp lại trước, sau đó tối ưu hóa dựa trên profiling. Thường một hàm đơn giản chạy trên luồng chính đủ tốt.
Go's Effective Go documentation provides detailed guidance. Here are key points:
Tài liệu Effective Go của Go cung cấp hướng dẫn chi tiết. Dưới đây là những điểm chính:
- Commentary: Write comments for every exported package, type, function, method. Comments should be complete sentences starting with the exported name.
Commentary: Viết comment cho mỗi exported package, type, function, method. Comments nên là câu hoàn chỉnh bắt đầu bằng tên được exported.
- Naming — MixedCaps not snake_case: Go uses MixedCaps for exported (CapitalCase), lowercase for unexported. No snake_case.
Naming — MixedCaps not snake_case: Go dùng MixedCaps (camelCase) cho exported names (CapitalCase), unexported (lowercase). Không dùng snake_case.
- Package names: short, lowercase, no underscores. The package name is the directory name. Variable names in the package should not repeat the package name.
Package names: ngắn, lowercase, không underscores. Package name là tên thư mục. Tên biến trong package không cần lặp tên package.
- Blank identifier: _ is used to discard values or import for side effects.
Blank identifier: _ được dùng để bỏ qua giá trị trả về hoặc import mà không sử dụng (side effects).
1// Good commenting2package user34// User represents a person in the system.5type User struct {6 // Name is the user's full name.7 Name string8 // Age is the user's age in years.9 Age int10}1112// NewUser creates a new User with the given name and age.Key Takeaways
Điểm Chính
- Use channels to pass data between goroutines, not shared memory with mutexesSử dụng channel để truyền dữ liệu giữa các goroutine, không phải shared memory với mutex
- Small, focused interfaces are better than large ones with many methodsCác interface nhỏ, tập trung tốt hơn các interface lớn với nhiều phương thức
- Functions should accept interfaces and return concrete structsHàm nên chấp nhận interface và trả về struct cụ thể
- Go prioritizes clarity and simplicity over cleverness and eleganceGo ưu tiên sự rõ ràng và đơn giản hơn sự thông minh và thanh lịch
Practice
Test your understanding of this chapter
According to Go proverbs, how should data be shared between goroutines?
Theo Go proverbs, dữ liệu nên được chia sẻ giữa các goroutine như thế nào?
What is the idiomatic Go pattern for function signatures?
Mẫu idiomatic Go cho function signature là gì?
Go uses snake_case (like my_variable) for variable naming conventions.
Go sử dụng snake_case (như my_variable) cho quy ước đặt tên biến.
According to Go philosophy, it is better to write clever code that is hard to understand than simple code that is easy to read.
Theo triết học Go, tốt hơn là viết code thông minh khó hiểu hơn là code đơn giản dễ đọc.
Fill in the Go naming convention for exported types
Điền vào quy ước đặt tên Go cho các kiểu được exported
// MyType is exported // myType is unexported