Gỡ bọc lỗi bằng `errors.Is`
Thuộc phần Logic và luồng điều khiển trong hành trình GO của Coddy. Bài học 40 trên 68.
Khi xử lý lỗi được bọc, bạn cần một cách để kiểm tra xem một lỗi cụ thể có xuất hiện ở bất kỳ đâu trong chuỗi lỗi hay không. Hàm errors.Is cung cấp chính xác khả năng này, giúp bạn nhận diện các lỗi sentinel ngay cả khi chúng đã được bọc nhiều lần.
Hàm errors.Is nhận hai tham số: lỗi bạn muốn kiểm tra và lỗi đích bạn đang tìm. Hàm trả về true nếu tìm thấy lỗi đích ở bất kỳ đâu trong chuỗi lỗi:
var ErrNotFound = errors.New("not found")
wrappedErr := fmt.Errorf("failed to load user: %w", ErrNotFound)
if errors.Is(wrappedErr, ErrNotFound) {
fmt.Println("This is a not found error")
}Đây là cách hiện đại và được khuyến nghị để kiểm tra các lỗi cụ thể trong Go. Khác với việc so sánh chuỗi hoặc kiểm tra bằng phép so sánh bằng trực tiếp, errors.Is hoạt động chính xác với các lỗi được bọc và lần theo chuỗi lỗi để tìm kết quả khớp. Nhờ đó, việc xử lý lỗi của bạn trở nên vững chắc và đáng tin cậy, ngay cả khi lỗi đi qua nhiều lớp trong ứng dụng.
Thử thách
DễXây dựng hệ thống giám sát kết nối cơ sở dữ liệu, sử dụng errors.Is để nhận diện các lỗi kết nối cụ thể trong chuỗi lỗi được bọc. Thử thách này minh họa cách dùng errors.Is để phát hiện lỗi sentinel ngay cả khi chúng được bọc thêm ngữ cảnh qua nhiều lớp ứng dụng.
Bạn sẽ nhận hai đầu vào:
- Một chuỗi chứa thông tin chi tiết về thao tác cơ sở dữ liệu theo định dạng
"database_name,operation,error_type"(ví dụ:"users_db,connect,connection_timeout") - Một chuỗi chứa ngữ cảnh ứng dụng theo định dạng
"service_name,retry_count"(ví dụ:"AuthService,3")
Nhiệm vụ của bạn:
- Định nghĩa ba lỗi sentinel bằng
errors.New():ErrConnectionTimeoutvới thông báo"connection timeout"ErrConnectionRefusedvới thông báo"connection refused"ErrDatabaseNotFoundvới thông báo"database not found"
- Phân tích đầu vào thứ nhất bằng cách tách chuỗi theo dấu phẩy để lấy tên cơ sở dữ liệu, thao tác và loại lỗi
- Phân tích đầu vào thứ hai bằng cách tách chuỗi theo dấu phẩy để lấy tên dịch vụ và số lần thử lại
- Tạo lỗi ban đầu dựa trên loại lỗi:
- Nếu loại lỗi là
"connection_timeout": dùngErrConnectionTimeout - Nếu loại lỗi là
"connection_refused": dùngErrConnectionRefused - Nếu loại lỗi là
"database_not_found": dùngErrDatabaseNotFound - Với mọi loại lỗi khác: dùng
errors.New("unknown database error")
- Nếu loại lỗi là
- Tạo lỗi được bọc ở cấp cơ sở dữ liệu bằng
fmt.Errorfvới%w:"failed to [operation] database [database_name]: %w" - Tạo lỗi được bọc ở cấp dịch vụ:
"[service_name] database operation failed after [retry_count] retries: %w" - Dùng
errors.Isđể kiểm tra xem lỗi được bọc cuối cùng có khớp với từng lỗi sentinel hay không, rồi hiển thị kết quả:"Checking for connection timeout: [true/false]""Checking for connection refused: [true/false]""Checking for database not found: [true/false]"
- Hiển thị chuỗi lỗi:
"Error Chain:""Original: [original_error_message]""Database level: [database_level_error_message]""Service level: [service_level_error_message]"
- Hiển thị phần phân tích lỗi:
"Error Analysis:""Database: [database_name]""Operation: [operation]""Service: [service_name]""Retries: [retry_count]""Error type detected: [detected_error_type]", trong đó detected_error_type là một trong các giá trị "Connection Timeout", "Connection Refused", "Database Not Found" hoặc "Unknown Error"
Dùng gói strings để tách các chuỗi đầu vào theo dấu phẩy, gói errors cho các lỗi sentinel và errors.Is, cùng gói fmt để bọc lỗi. Thử thách này minh họa cách errors.Is giúp nhận diện lỗi đáng tin cậy và hoạt động chính xác với các lỗi được bọc, nhờ đó trở thành công cụ thiết yếu để xử lý lỗi đáng tin cậy trong các ứng dụng phức tạp.
Tự làm thử
package main
import (
"errors"
"fmt"
"strings"
)
func main() {
// Đọc đầu vào
var dbOperation string
var appContext string
fmt.Scanln(&dbOperation)
fmt.Scanln(&appContext)
// Phân tích cú pháp đầu vào
dbParts := strings.Split(dbOperation, ",")
databaseName := dbParts[0]
operation := dbParts[1]
errorType := dbParts[2]
contextParts := strings.Split(appContext, ",")
serviceName := contextParts[0]
retryCount := contextParts[1]
// TODO: Viết code bên dưới
// 1. Định nghĩa ba lỗi sentinel bằng errors.New()
// 2. Tạo lỗi ban đầu dựa trên loại lỗi
// 3. Tạo lỗi bọc ở cấp cơ sở dữ liệu bằng fmt.Errorf với %w
// 4. Tạo lỗi bọc ở cấp dịch vụ
// 5. Dùng errors.Is để kiểm tra từng lỗi sentinel
// 6. Hiển thị kết quả và chuỗi lỗi
}Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Logic và luồng điều khiển
1Luồng điều khiển nâng cao
Switch với `fallthrough`Thoát khỏi vòng lặp lồng nhauTiếp tục một vòng lặp cụ thểCâu lệnh `goto`Ôn tập: điều khiển vòng lặp nâng cao4Dự án: Danh sách việc cần làm đơn giản
Thiết lập dự ánThêm một việc cần làm7Xử lý lỗi chuyên sâu
Kiểu lỗi tùy chỉnhBọc lỗi bằng `%w`Gỡ bọc lỗi bằng `errors.Is`Gỡ bọc lỗi bằng `errors.As`Tìm hiểu `panic`Sử dụng `recover`Ôn tập: phép chia an toàn2Struct và phương thức
Định nghĩa phương thức cho structReceiver giá trịReceiver con trỏChọn receiverPhương thức và hàmÔn tập: hành vi của struct5Tìm hiểu chuyên sâu về map
Map chứa structCon trỏ làm giá trị của mapKiểm tra map nilSo sánh các mapÔn tập: bộ đếm tần suất từ8Dự án: Quản lý hàng tồn kho đơn giản
Tổng quan dự ánKiểm tra hàng tồn kho3Interface (cơ bản)
Interface là gì?Định nghĩa interfaceTriển khai interfaceDùng kiểu interfaceInterface rỗngKhẳng định kiểuType switchÔn tập: hình dạng và hành viTự luyện tập: Trình biên dịch Go online