Menu
CoddyTech

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.

challenge icon

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:

    • ErrItemNotFound với thông báo "item not found in inventory"
    • ErrOutOfStock với thông báo "item is out of stock"
    • ErrInsufficientQuantity với thông báo "insufficient quantity available"

    Tạo một cấu trúc Inventory lư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() *Inventory tạo một kho hàng trống
    • AddItem(name string, quantity int) thêm mặt hàng hoặc cập nhật số lượng của mặt hàng
    • GetQuantity(name string) (int, error) trả về số lượng của một mặt hàng, hoặc ErrItemNotFound nếu mặt hàng đó không tồn tại
    • Purchase(name string, quantity int) error thử mua mặt hàng. Trả về ErrItemNotFound nếu mặt hàng không tồn tại, ErrOutOfStock nếu số lượng hiện tại bằng 0, hoặc ErrInsufficientQuantity nế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ượng 5, 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: in Error: Item '[name]' does not exist in our inventory
  • Nếu err == ErrOutOfStock: in Error: Item '[name]' is currently out of stock
  • Nếu err == ErrInsufficientQuantity: in Error: 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 inventory

Với laptop và 10, đầu ra của bạn sẽ là:

Error: Cannot purchase 10 units of 'laptop' - not enough in stock

Tự 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)
}
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