Bọc lỗi với fmt.Errorf
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 54 trên 107.
Khi lỗi đi qua nhiều lớp trong ứng dụng, việc biết lỗi bắt nguồn ở đâu trở nên rất quan trọng. Hàm (function) fmt.Errorf của Go với động từ %w cho phép bạn bọc lỗi, giữ nguyên lỗi ban đầu đồng thời bổ sung ngữ cảnh.
Việc bọc lỗi (error wrapping) tạo thành một chuỗi lỗi. Mỗi lớp có thể bổ sung thông tin về việc nó đang cố làm gì khi lỗi xảy ra:
func ReadConfig(filename string) error {
data, err := os.ReadFile(filename)
if err != nil {
return fmt.Errorf("reading config file: %w", err)
}
// xử lý dữ liệu...
return nil
}Động từ %w có chức năng đặc biệt. Nó bọc lỗi ban đầu bên trong lỗi mới. Khác với %v, chỉ chuyển lỗi thành chuỗi và làm mất danh tính của lỗi ban đầu.
func LoadSettings() error {
err := ReadConfig("settings.json")
if err != nil {
return fmt.Errorf("loading settings: %w", err)
}
return nil
}Khi LoadSettings thất bại, thông báo lỗi hiển thị toàn bộ chuỗi: "loading settings: reading config file: open settings.json: no such file or directory". Mỗi lớp bổ sung ngữ cảnh, giúp việc gỡ lỗi dễ dàng hơn nhiều.
Lỗi được bọc vẫn chứa lỗi gốc bên trong. Trong bài học tiếp theo, bạn sẽ học cách mở bọc các lỗi này và kiểm tra bên trong bằng errors.Is() và errors.As().
Thử thách
DễHãy xây dựng một hệ thống xử lý đơn hàng để minh họa cách bọc lỗi qua nhiều lớp. Bạn sẽ tạo một chuỗi hàm (function), trong đó mỗi lớp bổ sung ngữ cảnh cho lỗi, giúp dễ dàng xác định chính xác vấn đề xảy ra ở đâu.
Bạn sẽ sắp xếp code trong hai tệp:
orders.go: Tạo logic xử lý đơn hàng với nhiều lớp bọc lỗi khi lỗi truyền lên các lớp phía trên.Triển khai ba hàm tạo thành một chuỗi xử lý:
ValidateItem(itemID string) error- Trả về một lỗi với thông báo"item not found"nếu itemID là"INVALID", nếu không thì trả vềnilProcessOrder(orderID, itemID string) error- GọiValidateItem. Nếu hàm này trả về lỗi, hãy bọc lỗi đó với ngữ cảnh:"processing order [orderID]: %w". Nếu không thì trả vềnilSubmitOrder(customerName, orderID, itemID string) error- GọiProcessOrder. Nếu hàm này trả về lỗi, hãy bọc lỗi đó với ngữ cảnh:"submitting order for [customerName]: %w". Nếu không thì trả vềnil
Mỗi lớp nên dùng
fmt.Errorfvới động từ%wđể bọc lỗi từ lớp bên dưới, tạo thành một chuỗi ngữ cảnh.main.go: Đọc thông tin đơn hàng từ đầu vào, gọiSubmitOrdervà hiển thị kết quả. Nếu xảy ra lỗi, hãy in toàn bộ chuỗi lỗi. Nếu thành công, hãy in thông báo xác nhận.
Chương trình sẽ nhận các đầu vào sau:
- Dòng 1: Tên khách hàng
- Dòng 2: Mã đơn hàng
- Dòng 3: Mã mặt hàng
In kết quả:
- Nếu xảy ra lỗi:
Error: [full error chain] - Nếu thành công:
Order [orderID] submitted successfully for [customerName]
Ví dụ, với các đầu vào Alice, ORD-123 và INVALID, đầu ra của bạn sẽ là:
Error: submitting order for Alice: processing order ORD-123: item not foundHãy chú ý cách thông báo lỗi hiển thị toàn bộ chuỗi. Bạn có thể lần theo vấn đề từ thao tác gửi đơn hàng ở lớp cao nhất, qua bước xử lý đơn hàng, đến lỗi xác thực thực tế. Mỗi lớp bổ sung ngữ cảnh riêng bằng %w.
Với các đầu vào Bob, ORD-456 và ITEM-001, đầu ra của bạn sẽ là:
Order ORD-456 submitted successfully for BobTự làm thử
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Đọc tên khách hàng
scanner.Scan()
customerName := scanner.Text()
// Đọc ID đơn hàng
scanner.Scan()
orderID := scanner.Text()
// Đọc ID mặt hàng
scanner.Scan()
itemID := scanner.Text()
// TODO: Gọi SubmitOrder với các giá trị đầu vào
// TODO: Nếu xảy ra lỗi, in ra: Error: [full error chain]
// TODO: Nếu thành công, in ra: Order [orderID] submitted successfully for [customerName]
_ = customerName
_ = orderID
_ = itemID
}
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