Lỗi sentinel
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 55 trên 107.
Lỗi sentinel là một biến lỗi được định nghĩa trước ở cấp package, đại diện cho một điều kiện lỗi cụ thể. Khác với việc tạo lỗi mới mỗi lần bằng errors.New, lỗi sentinel được khai báo một lần và dùng lại trong toàn bộ code của bạn.
Bạn định nghĩa các lỗi sentinel dưới dạng biến ở cấp package, thường có tên bắt đầu bằng Err:
package user
import "errors"
var (
ErrNotFound = errors.New("user not found")
ErrInvalidEmail = errors.New("invalid email address")
ErrDuplicate = errors.New("user already exists")
)Các hàm trả về những lỗi được xác định trước này khi xảy ra các điều kiện cụ thể:
func FindUser(id int) (*User, error) {
user, exists := users[id]
if !exists {
return nil, ErrNotFound
}
return user, nil
}Ưu điểm của lỗi sentinel (sentinel error) là bên gọi có thể kiểm tra các điều kiện lỗi cụ thể bằng cách so sánh trực tiếp các giá trị lỗi:
user, err := FindUser(42)
if err == ErrNotFound {
fmt.Println("Creating new user...")
} else if err != nil {
fmt.Println("Unexpected error:", err)
}Thư viện chuẩn của Go sử dụng rộng rãi các lỗi sentinel. Ví dụ, io.EOF báo hiệu đầu vào đã kết thúc, còn sql.ErrNoRows cho biết kết quả truy vấn trống. Cách làm này tạo ra một giao ước rõ ràng giữa các package và người dùng về những lỗi có thể gặp.
Tuy nhiên, phép kiểm tra bằng nhau đơn giản sẽ không hoạt động khi lỗi được bọc. Trong bài học tiếp theo, bạn sẽ tìm hiểu cách errors.Is() giải quyết vấn đề này bằng cách kiểm tra toàn bộ chuỗi lỗi.
Thử thách
DễHãy xây dựng một hệ thống quản lý hàng tồn kho dùng các lỗi sentinel để thông báo những tình huống thất bại cụ thể. Bạn sẽ định nghĩa các biến lỗi ở cấp package để bên gọi có thể kiểm tra và xử lý từng tình huống phù hợp.
Bạn sẽ sắp xếp code trong hai tệp:
inventory.go: Tạo hệ thống quản lý hàng tồn kho với các lỗi sentinel được định nghĩa sẵn cho những trường hợp thất bại thường gặp.Định nghĩa ba lỗi sentinel ở cấp package:
ErrItemNotFoundvới thông báo"item not found in inventory"ErrOutOfStockvới thông báo"item is out of stock"ErrInsufficientQuantityvới thông báo"insufficient quantity available"
Tạo một cấu trúc
Inventorylưu một map tên mặt hàng và số lượng tương ứng. Cài đặt các phương thức sau:NewInventory() *Inventorytạo một kho hàng trốngAddItem(name string, quantity int)thêm mặt hàng hoặc cập nhật số lượng của mặt hàngGetQuantity(name string) (int, error)trả về số lượng của một mặt hàng, hoặcErrItemNotFoundnếu mặt hàng đó không tồn tạiPurchase(name string, quantity int) errorthử mua mặt hàng. Trả vềErrItemNotFoundnếu mặt hàng không tồn tại,ErrOutOfStocknếu số lượng hiện tại bằng 0, hoặcErrInsufficientQuantitynếu số lượng yêu cầu vượt quá lượng hàng hiện có. Nếu thành công, giảm số lượng trong kho và trả vềnil.
main.go: Đọc các thao tác với kho hàng từ đầu vào và minh họa cách bên gọi kiểm tra từng lỗi sentinel bằng phép so sánh bằng trực tiếp.Đọc tên mặt hàng và số lượng cần mua từ đầu vào. Tạo một kho hàng, thêm mặt hàng
"laptop"với số lượng5, sau đó thử mua mặt hàng. Dựa trên lỗi trả về, in một thông báo cụ thể để cho biết bạn đã xác định được lỗi sentinel nào xảy ra.
Đầu vào sẽ gồm:
- Dòng 1: Tên mặt hàng cần mua
- Dòng 2: Số lượng cần mua
Xử lý kết quả bằng cách kiểm tra lỗi cụ thể được trả về:
- Nếu
err == ErrItemNotFound: inError: Item '[name]' does not exist in our inventory - Nếu
err == ErrOutOfStock: inError: Item '[name]' is currently out of stock - Nếu
err == ErrInsufficientQuantity: inError: Cannot purchase [quantity] units of '[name]' - not enough in stock - Nếu thành công: in
Successfully purchased [quantity] units of '[name]'
Ví dụ, với laptop và 3, đầu ra của bạn sẽ là:
Successfully purchased 3 units of 'laptop'Với phone và 2, đầu ra của bạn sẽ là:
Error: Item 'phone' does not exist in our inventoryVới laptop và 10, đầu ra của bạn sẽ là:
Error: Cannot purchase 10 units of 'laptop' - not enough in stockTự làm thử
package main
import (
"fmt"
)
func main() {
// Đọc đầu vào
var itemName string
var quantity int
fmt.Scanln(&itemName)
fmt.Scanln(&quantity)
// Tạo kho hàng và thêm "laptop" với số lượng 5
inv := NewInventory()
inv.AddItem("laptop", 5)
// TODO: Thử mua mặt hàng
// Gọi inv.Purchase(itemName, quantity)
// TODO: Dùng phép so sánh bằng trực tiếp để kiểm tra lỗi sentinel cụ thể nào được trả về
// Dùng các câu lệnh if/else if để kiểm tra:
// - err == ErrItemNotFound
// - err == ErrOutOfStock
// - err == ErrInsufficientQuantity
// - err == nil (thành công)
// TODO: In ra thông báo phù hợp với lỗi
// Nếu ErrItemNotFound: fmt.Printf("Error: Item '%s' does not exist in our inventory\n", itemName)
// Nếu ErrOutOfStock: fmt.Printf("Error: Item '%s' is currently out of stock\n", itemName)
// Nếu ErrInsufficientQuantity: fmt.Printf("Error: Cannot purchase %d units of '%s' - not enough in stock\n", quantity, itemName)
// Nếu thành công: fmt.Printf("Successfully purchased %d units of '%s'\n", quantity, itemName)
}
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