Menu
CoddyTech

errors.Is() và errors.As()

Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 56 trên 107.

Khi lỗi được bọc, các phép kiểm tra bằng nhau đơn giản như err == ErrNotFound sẽ không còn hoạt động. Gói errors cung cấp hai hàm để kiểm tra chuỗi lỗi đã bọc: errors.Is() và errors.As().

errors.Is() kiểm tra xem có lỗi nào trong chuỗi lỗi khớp với một lỗi sentinel cụ thể hay không:

import "errors"

var ErrNotFound = errors.New("not found")

func FindUser(id int) error {
    return fmt.Errorf("database lookup failed: %w", ErrNotFound)
}

err := FindUser(42)
if errors.Is(err, ErrNotFound) {
    fmt.Println("User doesn't exist")
}

Mặc dù lỗi được trả về đã được bọc, errors.Is() vẫn gỡ chuỗi lỗi và tìm thấy ErrNotFound bên trong.

errors.As() kiểm tra xem có lỗi nào trong chuỗi khớp với một kiểu cụ thể hay không và trích xuất lỗi đó:

type ValidationError struct {
    Field string
}

func (e *ValidationError) Error() string {
    return "validation failed: " + e.Field
}

err := fmt.Errorf("request failed: %w", &ValidationError{Field: "email"})

var ve *ValidationError
if errors.As(err, &ve) {
    fmt.Println("Invalid field:", ve.Field)
}

Dùng errors.Is() khi kiểm tra các lỗi sentinel, và dùng errors.As() khi cần truy cập các trường của một kiểu lỗi tùy chỉnh. Cả hai hàm đều duyệt toàn bộ chuỗi lỗi, nên rất cần thiết để xử lý đúng các lỗi đã được bọc.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống xử lý tệp để minh họa cách kiểm tra đúng cách chuỗi lỗi được bọc bằng errors.Is() và errors.As(). Bạn sẽ tạo các lỗi được bọc khi đi qua những lớp khác nhau, sau đó dùng các hàm này để xác định và trích xuất thông tin từ chúng.

Bạn sẽ sắp xếp code trong hai tệp:

  • errors.go: Định nghĩa cơ sở hạ tầng xử lý lỗi cho hệ thống tệp.

    Tạo một lỗi sentinel:

    • ErrPermissionDenied với thông báo "permission denied"

    Tạo một kiểu lỗi tùy chỉnh:

    • struct FileError có các trường Filename (chuỗi) và Operation (chuỗi)
    • Phương thức Error() của kiểu này sẽ trả về: [Operation] failed on file: [Filename]

    Triển khai hai hàm mô phỏng các thao tác với tệp và bọc lỗi:

    • ReadFile(filename string) error - Nếu filename là "secret.txt", trả về ErrPermissionDenied. Nếu filename là "missing.txt", trả về *FileError với Operation là "read" và Filename là giá trị đã cho. Trong các trường hợp khác, trả về nil.
    • ProcessFile(filename string) error - Gọi ReadFile. Nếu hàm trả về lỗi, bọc lỗi đó bằng fmt.Errorf với định dạng "processing failed: %w". Nếu không, trả về nil.
  • main.go: Đọc tên tệp từ đầu vào và gọi ProcessFile. Dùng errors.Is() và errors.As() để kiểm tra chuỗi lỗi được bọc và in thông báo phù hợp dựa trên nội dung tìm thấy.

Đầu vào sẽ có định dạng như sau:

  • Dòng 1: Tên tệp cần xử lý

Xử lý kết quả bằng cách kiểm tra chuỗi lỗi:

  • Nếu errors.Is() tìm thấy ErrPermissionDenied trong chuỗi: in toàn bộ thông báo lỗi, sau đó in Access denied - check file permissions trên dòng mới
  • Nếu errors.As() tìm thấy *FileError trong chuỗi: in toàn bộ thông báo lỗi, sau đó in File issue: [Filename] during [Operation] trên dòng mới
  • Nếu không xảy ra lỗi: in File '[filename]' processed successfully

Ví dụ, với đầu vào secret.txt, kết quả sẽ là:

processing failed: permission denied
Access denied - check file permissions

Với đầu vào missing.txt, kết quả sẽ là:

processing failed: read failed on file: missing.txt
File issue: missing.txt during read

Với đầu vào data.txt, kết quả sẽ là:

File 'data.txt' processed successfully

Lưu ý rằng dù các lỗi được ProcessFile bọc lại, bạn vẫn có thể phát hiện lỗi sentinel ban đầu bằng errors.Is() và trích xuất kiểu lỗi tùy chỉnh bằng errors.As(). Các hàm này duyệt toàn bộ chuỗi lỗi để tìm nội dung bạn cần.

Tự làm thử

package main

import (
	"errors"
	"fmt"
)

func main() {
	// Đọc tên tệp từ đầu vào
	var filename string
	fmt.Scanln(&filename)

	// Gọi hàm (function) ProcessFile với tên tệp
	err := ProcessFile(filename)

	// TODO: Kiểm tra chuỗi lỗi để xử lý kết quả
	// 
	// Dùng errors.Is() để kiểm tra ErrPermissionDenied có trong chuỗi lỗi không:
	// Nếu có: in ra toàn bộ lỗi, rồi in "Access denied - check file permissions"
	//
	// Dùng errors.As() để kiểm tra chuỗi lỗi có *FileError không:
	// Nếu có: in ra toàn bộ lỗi, rồi in "File issue: [Filename] during [Operation]"
	//
	// Nếu không có lỗi: in "File '[filename]' processed successfully"

	_ = errors.Is // gợi ý: dùng errors.Is()
	_ = errors.As // gợi ý: dùng errors.As()
	_ = err
}
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 Lập trình hướng đối tượng

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