Sự phát triển của xử lý lỗi trong Go
Trong những ngày đầu sự nghiệp Gopher, tôi thường cảm thấy nản lòng với cách Go xử lý lỗi. Chuyển sang từ các ngôn ngữ có khối try-catch, việc lặp đi lặp lại if err != nil tạo cảm giác rườm rà. Tuy nhiên, khi xây dựng các hệ thống lớn hơn, tôi nhận ra rằng việc xử lý lỗi tường minh của Go thực sự là thế mạnh lớn nhất của nó. Nó buộc bạn phải suy nghĩ về các trường hợp thất bại ở mọi bước.
Trước phiên bản Go 1.13, chúng ta bị giới hạn trong các sentinel error đơn giản (như io.EOF) hoặc kiểm tra các chuỗi lỗi (error strings). Điều này khiến việc thêm ngữ cảnh vào lỗi mà không làm mất đi danh tính của lỗi gốc trở nên khó khăn. Nếu bạn bọc một lỗi trong một chuỗi mới, bạn không còn có thể kiểm tra xem ban đầu nó có phải là lỗi ‘Not Found’ hay không mà không qua các bước xử lý chuỗi phức tạp.
So sánh các phương pháp: Cách tiếp cận cũ và Hiện đại (Wrapping)
Để hiểu tại sao chúng ta sử dụng errors.Is và errors.As, chúng ta cần xem xét cách mọi thứ đã từng được thực hiện so với tiêu chuẩn hiện đại.
Cách truyền thống (Trước Go 1.13)
var ErrNotFound = errors.New("không tìm thấy")
func findUser(id string) error {
return fmt.Errorf("người dùng %s: %v", id, ErrNotFound)
}
// Kiểm tra lỗi
err := findUser("123")
if err == ErrNotFound { // Thất bại! Lỗi bây giờ là một chuỗi khác.
// Xử lý khi không tìm thấy
}
Cách hiện đại (Go 1.13+)
func findUser(id string) error {
return fmt.Errorf("người dùng %s: %w", id, ErrNotFound) // Chú ý %w
}
// Kiểm tra lỗi
err := findUser("123")
if errors.Is(err, ErrNotFound) { // Thành công! Nó sẽ unwrap chuỗi lỗi.
// Xử lý khi không tìm thấy
}

