Menu
CoddyTech

哨兵错误

本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 55 课,共 107 课。

哨兵错误是一种预定义的包级错误变量,用于表示特定的错误条件。与每次都使用 errors.New 创建新错误不同,哨兵错误只声明一次,并在代码中重复使用。

你将哨兵错误定义为包级变量,通常使用以 Err 开头的名称:

package user

import "errors"

var (
    ErrNotFound     = errors.New("user not found")
    ErrInvalidEmail = errors.New("invalid email address")
    ErrDuplicate    = errors.New("user already exists")
)

在特定条件出现时,函数会返回这些预定义的错误:

func FindUser(id int) (*User, error) {
    user, exists := users[id]
    if !exists {
        return nil, ErrNotFound
    }
    return user, nil
}

哨兵错误的优势在于,调用方可以通过直接比较错误值来检查特定的错误条件:

user, err := FindUser(42)
if err == ErrNotFound {
    fmt.Println("Creating new user...")
} else if err != nil {
    fmt.Println("Unexpected error:", err)
}

Go 的标准库广泛使用哨兵错误。例如,io.EOF 表示输入结束,sql.ErrNoRows 表示查询结果为空。这种模式让包及其使用者之间就预期会出现哪些错误形成了明确的约定。

不过,错误被包装后,简单的相等性检查就会失效。在下一课中,你将学习 errors.Is() 如何通过检查整个错误链来解决这个问题。

challenge icon

挑战

简单

我们来构建一个库存管理系统,使用哨兵错误来表示特定的失败情况。你将定义包级错误变量,调用方可以检查这些变量,以便妥善处理不同情况。

你将把代码分成两个文件:

  • inventory.go:创建一个库存系统,并为常见的失败情况预定义哨兵错误。

    在包级别定义三个哨兵错误:

    • ErrItemNotFound,消息为 "item not found in inventory"
    • ErrOutOfStock,消息为 "item is out of stock"
    • ErrInsufficientQuantity,消息为 "insufficient quantity available"

    创建一个 Inventory 结构体,用于存储商品名称及其数量的映射。实现以下方法:

    • NewInventory() *Inventory:创建一个空库存
    • AddItem(name string, quantity int):添加商品或更新商品数量
    • GetQuantity(name string) (int, error):返回商品数量;如果商品不存在,则返回 ErrItemNotFound
    • Purchase(name string, quantity int) error:尝试购买商品。如果商品不存在,则返回 ErrItemNotFound;如果当前数量为零,则返回 ErrOutOfStock;如果请求的数量超过现有库存,则返回 ErrInsufficientQuantity。购买成功时,减少库存并返回 nil。
  • main.go:从输入中读取库存操作,并演示调用方如何使用直接相等比较来检查特定的哨兵错误。

    从输入中读取商品名称和购买数量。创建一个库存,将数量为 5 的 "laptop" 添加到库存中,然后尝试购买。根据返回的错误,打印一条特定消息,说明你识别出了发生的哨兵错误。

输入内容如下:

  • 第 1 行:要购买的商品名称
  • 第 2 行:要购买的数量

检查返回的是哪种特定错误,并处理结果:

  • 如果 err == ErrItemNotFound:打印 Error: Item '[name]' does not exist in our inventory
  • 如果 err == ErrOutOfStock:打印 Error: Item '[name]' is currently out of stock
  • 如果 err == ErrInsufficientQuantity:打印 Error: Cannot purchase [quantity] units of '[name]' - not enough in stock
  • 如果购买成功:打印 Successfully purchased [quantity] units of '[name]'

例如,输入 laptop 和 3,输出应为:

Successfully purchased 3 units of 'laptop'

输入 phone 和 2,输出应为:

Error: Item 'phone' does not exist in our inventory

输入 laptop 和 10,输出应为:

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

亲手试试

package main

import (
	"fmt"
)

func main() {
	// 读取输入
	var itemName string
	var quantity int
	fmt.Scanln(&itemName)
	fmt.Scanln(&quantity)

	// 创建库存,并添加数量为 5 的 "laptop"
	inv := NewInventory()
	inv.AddItem("laptop", 5)

	// TODO: 尝试购买该商品
	// 调用 inv.Purchase(itemName, quantity)

	// TODO: 使用直接相等比较,检查返回的是哪个哨兵错误
	// 使用 if/else if 语句检查:
	// - err == ErrItemNotFound
	// - err == ErrOutOfStock
	// - err == ErrInsufficientQuantity
	// - err == nil(成功)

	// TODO: 根据错误输出相应的消息
	// 如果是 ErrItemNotFound:fmt.Printf("Error: Item '%s' does not exist in our inventory\n", itemName)
	// 如果是 ErrOutOfStock:fmt.Printf("Error: Item '%s' is currently out of stock\n", itemName)
	// 如果是 ErrInsufficientQuantity:fmt.Printf("Error: Cannot purchase %d units of '%s' - not enough in stock\n", quantity, itemName)
	// 如果购买成功:fmt.Printf("Successfully purchased %d units of '%s'\n", quantity, itemName)
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Go 在线编译器