Effective Go: Generics, Interfaces và Error Handling Patterns

Effective Go 2024: Generics, Interfaces và Error Handling Patterns

Go 1.18 ra mắt generics năm 2022, nhưng đến 2024 mới thực sự định hình thành các patterns phổ biến trong production code. Nhiều team vẫn viết Go theo style cũ — interface giả trừu tượng, error wrapping thủ công, factory function lung tung — khiến codebase trở nên cồng kềnh và khó maintain. Bài này tập trung vào patterns thực tế mà bạn có thể áp dụng ngay hôm nay.

Trước generics, Go developer phải dùng interface{} hoặc code generation (stringer, mockgen) để xử lý kiểu dữ liệu tổng quát. Điều này tạo ra hai vấn đề lớn: mất type safety tại compile time và boilerplate code dài dòng. Generics giải quyết được cả hai bằng type parameters, nhưng cũng mang theo trade-off: compile time tăng, binary size phình to, và template instantiation phức tạp khi overuse.

Lập trình Go với generics và patterns hiện đại

Generic constraints với kiểu đơn giản

Pattern đầu tiên là constraints package cho những hàm thuật toán cần kiểu so sánh. Thay vì viết func Max[T interface{ Less ~int }] mỗi lần, bạn tạo một constraints.Ordered tập hợp tất cả kiểu comparable hỗ trợ toán tử <>. Điều này tái sử dụng được xuyên package và cho IDE biết chính xác constraint set.

Ví dụ thực tế: func Min[T constraints.Ordered](a, b T) T hoạt động cho int, float64, string mà không cần mở rộng interface mỗi lần. Khi bạn cần custom ordering — ví dụ struct User theo tuổi — dùng constraints.Ordered không đủ, bạn viết type UserAge interface { ~int } hoặc implement Less method riêng.

Error wrapping chuẩn Go 1.13+

Go 1.13 giới thiệu %w verb trong fmt.Errorf, cho phép wrap error mà giữ nguyên unwrap chain. Pattern phổ biến hiện nay là: custom error type với Unwrap() error, Is() method cho sentinel check, và As() để cast về concrete type.

Tránh anti-pattern này: if err != nil { return fmt.Errorf("failed: %w", err) } mù mà không context. Thay vào đó: return fmt.Errorf("connect db %s timeout: %w", dsn, err) — thêm action + resource vào message. Nhiều team dùng errors.Is cho sentinel (ErrNotFound) và errors.As cho typed error (*validationError) thay vì string matching.

Go error wrapping pattern với %w verb

Interface segregation trong Go

SOLID không phải keyword bắt buộc trong Go, nhưng io.Readerio.Writer là ví dụ điển hình interface segregation: mỗi interface chỉ một method, consumer chỉ depend vào capability họ thực sự cần. Pattern này gọi là “accept interfaces, return structs” — nhận parameter kiểu interface, trả về concrete type để caller linh hoạt.

Tránh interface bloat: nếu bạn có interface Repository với 12 methods nhưng consumer chỉ dùng 3, split ra. Điều này đặc biệt quan trọng khi unit test: mock nhỏ dễ viết, dễ maintain. Go 1.18+ cho phép embedding interface, nên bạn compose type Reader interface { io.Reader } thay vì copy method.

Context pattern cho cancellation và timeout

context.Context là first-class parameter trong Go từ phiên bản 1.7. Pattern chuẩn: function nhận ctx context.Context làm parameter đầu tiên, propagate xuống tất cả downstream call. Đừng store context vào struct — nó là ephemeral value, chỉ sống trong một request lifecycle.

Với HTTP handler: http.Request.Context() tự động cancel khi client đóng kết nối. Với database: sql.DBBeginTx(ctx), QueryContext(ctx). Với gRPC: context metadata carry authentication token. Tận hưởng context.WithTimeoutcontext.WithDeadline thay vì hardcoded time.After.

Khi nào dùng generics, khi nào không

Generics phù hợp cho thuật toán tổng quát: Map[T, U], Filter[T], Reduce[T, U], data structure như Set[T], PriorityQueue[T]. Không phù hợp khi: business logic chỉ dùng một concrete type, performance-critical path compile time chậm, hoặc khi dùng interface{} với reflection đã đủ.

Benchmark thực tế cho thấy generic code chạy nhanh hơn interface{} + reflection khoảng 20-30%, nhưng chậm hơn concrete code 5-10%. Nếu bạn đang viết web server, database access layer, CLI tool — generic đáng cân nhắc. Nếu bạn viết kernel-level code, real-time trading — concrete type vẫn là vua.

Code review Go generics implementation

Kết luận

Go 2024 không còn là ngôn ngữ “đơn giản thiếu tính năng”. Với generics, improved error handling, và pattern rõ ràng, Go trở thành lựa chọn mạnh mẽ cho cloud-native backend, CLI tools, và infrastructure software. Tập trung vào: constraints rõ ràng, error wrapping có context, interface nhỏ gọn, và context propagation đúng đắn. Đây là nền tảng codebase Go maintainable trong dài hạn.

Nguồn tham khảo

Tôi là một lập trình viên IOS. Code chính là IOS nhưng thỉnnh thoảng vẫn đá sang Android hoặc web. Mặc dù không quá thông thạo nhưng tôi sẽ chia sẻ những kiến thức mà mình đã tìm hiểu, áp dụng qua.

Bài viết liên quan

C++ là gì? Tổng quan ngôn ngữ lập trình hệ thống hiệu năng cao

C++ là gì? Tổng quan ngôn ngữ lập trình hệ thống hiệu năng cao C++ là ngôn ngữ lập trình đa năng, hiệu năng cao, được sử dụng rộng rãi…

Xem thêm

Database Sharding là gì? Khi scaling đỉnh cao đơn thuần không đủ

Database Sharding là gì? Khi scaling đỉnh cao đơn thuần không đủ Database sharding (phân mảnh) là kỹ thuật chia dữ liệu trong một database lớn thành nhiều mảnh nhỏ…

Xem thêm

Zig Package Manager: quản lý dependencies và build cross-compiler tự động

Zig Package Manager là gì? Zig Package Manager (ZPM) là hệ thống quản lý dependencies tích hợp sẵn trong ngôn ngữ lập trình Zig, ra đời để giải quyết bài…

Xem thêm
0 0 đánh giá
Article Rating
Theo dõi
Thông báo của
guest
0 Comments
Cũ nhất
Mới nhất Được bỏ phiếu nhiều nhất