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.
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:
ErrPermissionDeniedvới thông báo"permission denied"
Tạo một kiểu lỗi tùy chỉnh:
- struct
FileErrorcó các trườngFilename(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ề*FileErrorvớ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ọiReadFile. Nếu hàm trả về lỗi, bọc lỗi đó bằngfmt.Errorfvớ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ọiProcessFile. Dùngerrors.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ấyErrPermissionDeniedtrong chuỗi: in toàn bộ thông báo lỗi, sau đó inAccess denied - check file permissionstrên dòng mới - Nếu
errors.As()tìm thấy*FileErrortrong chuỗi: in toàn bộ thông báo lỗi, sau đó inFile 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 permissionsVới đầu vào missing.txt, kết quả sẽ là:
processing failed: read failed on file: missing.txt
File issue: missing.txt during readVới đầu vào data.txt, kết quả sẽ là:
File 'data.txt' processed successfullyLư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
}
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
1Nền tảng OOP trong Go
Tệp bên ngoàiWorkspace và module trong GoPackage và importTên được export và không được exportGiới thiệu về OOP trong GoStruct như lớpĐịnh nghĩa phương thức cho structReceiver kiểu con trỏ và kiểu giá trịKhởi tạo structHàm khởi tạoÔn tập: máy tính đơn giản4Interface
Giới thiệu về interfaceTriển khai ngầm địnhInterface như một hợp đồngInterface rỗng (any)Khẳng định kiểuswitch kiểuKết hợp interfaceInterface Stringer và ErrorÔn tập: Máy tính hình học7Tính đóng gói
Trường được xuất và không được xuấtĐóng gói ở cấp packagePhương thức getter và setterẨn thông tin trong GoÔn tập: hồ sơ học sinh10Generics (Go 1.18 trở lên)
Giới thiệu về GenericsTham số kiểuRàng buộc kiểuStruct tổng quátGiải pháp thay thế cho phương thức tổng quátÔn tập: tập hợp tổng quát13Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonMẫu FactoryMẫu Abstract FactoryMẫu ObserverMẫu Strategy2Tìm hiểu chuyên sâu về kiểu dữ liệu và struct
Kiểu cơ bản và kiểu tổng hợpĐịnh nghĩa kiểu dữ liệu tùy chỉnhThẻ structStruct ẩn danhStruct lồng nhauGiá trị zero và giá trị mặc địnhÔn tập: sổ liên lạc5Ưu tiên kết hợp hơn kế thừa
Vì sao Go không có kế thừaNhập môn nhúng structTự động nâng phương thứcNhúng nhiều structNhúng và kết tậpChe khuất phương thức được nhúngÔn tập: hệ phân cấp nhân viên8Xử lý lỗi và lập trình hướng đối tượng
Giao diện errorCác kiểu lỗi tùy chỉnhBọc lỗi với fmt.ErrorfLỗi sentinelerrors.Is() và errors.As()Panic, defer và recoverÔn tập: trình phân tích tệp3Con trỏ và bộ nhớ
Cơ bản về con trỏ trong GoCon trỏ đến structTruyền theo giá trị và tham chiếuHàm new()Thu gom rác trong GoÔn tập: xây dựng danh sách liên kết6Đa hình trong Go
Đa hình qua interfaceĐịnh kiểu vịt trong GoQuy tắc thỏa mãn interfaceCác tập hợp đa hìnhTiêm phụ thuộcÔn tập: bộ xử lý thanh toán9Lập trình đồng thời và lập trình hướng đối tượng
Cơ bản về goroutineChannel và giao tiếpChannel có bộ đệm và không có bộ đệmCâu lệnh selectsync.Mutex và sync.RWMutexsync.WaitGroupThiết kế struct an toàn luồngÔn tập: worker poolTự luyện tập: Trình biên dịch Go online