Menu
CoddyTech

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.

challenge icon

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:

  1. Định nghĩa ba lỗi sentinel bằng errors.New():
    • ErrConnectionTimeout với thông báo "connection timeout"
    • ErrConnectionRefused với thông báo "connection refused"
    • ErrDatabaseNotFound với thông báo "database not found"
  2. 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
  3. 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
  4. Tạo lỗi ban đầu dựa trên loại lỗi:
    • Nếu loại lỗi là "connection_timeout": dùng ErrConnectionTimeout
    • Nếu loại lỗi là "connection_refused": dùng ErrConnectionRefused
    • Nếu loại lỗi là "database_not_found": dùng ErrDatabaseNotFound
    • Với mọi loại lỗi khác: dùng errors.New("unknown database error")
  5. Tạo lỗi được bọc ở cấp cơ sở dữ liệu bằng fmt.Errorf với %w: "failed to [operation] database [database_name]: %w"
  6. Tạo lỗi được bọc ở cấp dịch vụ: "[service_name] database operation failed after [retry_count] retries: %w"
  7. 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]"
  8. 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]"
  9. 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
	
}
quiz iconTự kiểm tra

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

Tự luyện tập: Trình biên dịch Go online