Menu
CoddyTech

错误包装(fmt.Errorf)

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

当错误经过应用的多个层级时,了解错误的来源至关重要。Go 的 fmt.Errorf 函数配合 %w 动词,可以让你包装错误,在保留原始错误的同时添加上下文。

错误包装会形成一连串错误。每一层都可以补充错误发生时正在尝试执行的操作的信息:

func ReadConfig(filename string) error {
    data, err := os.ReadFile(filename)
    if err != nil {
        return fmt.Errorf("reading config file: %w", err)
    }
    // 处理数据...
    return nil
}

%w 格式说明符很特殊。它会将原始错误包装在新错误中。这与 %v 不同,%v 只会将错误转换为字符串,并丢失原始错误的身份。

func LoadSettings() error {
    err := ReadConfig("settings.json")
    if err != nil {
        return fmt.Errorf("loading settings: %w", err)
    }
    return nil
}

当 LoadSettings 失败时,错误消息会显示完整的错误链:"loading settings: reading config file: open settings.json: no such file or directory"。每一层都会补充上下文,让调试变得容易得多。

包装后的错误中仍然包含原始错误。在下一课中,你将学习如何解开这些错误,并使用 errors.Is() 和 errors.As() 检查其中的内容。

challenge icon

挑战

简单

我们来构建一个订单处理系统,演示如何在多个层级中包装错误。你将创建一系列函数,每一层都会为错误添加上下文,方便你准确追踪问题发生的位置。

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

  • orders.go:创建分层的订单处理逻辑,在错误向上层传递时逐层包装错误。

    实现以下三个函数,组成处理链:

    • ValidateItem(itemID string) error - 如果 itemID 是 "INVALID",则返回错误,错误消息为 "item not found";否则返回 nil
    • ProcessOrder(orderID, itemID string) error - 调用 ValidateItem。如果它返回错误,就用以下上下文包装错误:"processing order [orderID]: %w"。否则返回 nil
    • SubmitOrder(customerName, orderID, itemID string) error - 调用 ProcessOrder。如果它返回错误,就用以下上下文包装错误:"submitting order for [customerName]: %w"。否则返回 nil

    每一层都应使用 fmt.Errorf 和 %w 动词包装下一层传来的错误,逐步构建错误上下文链。

  • main.go:从输入中读取订单详情,调用 SubmitOrder 并显示结果。如果发生错误,就打印完整的错误链。如果成功,就打印确认消息。

输入将按以下格式提供:

  • 第 1 行:客户姓名
  • 第 2 行:订单 ID
  • 第 3 行:商品 ID

打印结果:

  • 如果发生错误:Error: [full error chain]
  • 如果成功:Order [orderID] submitted successfully for [customerName]

例如,输入 Alice、ORD-123 和 INVALID 后,输出应为:

Error: submitting order for Alice: processing order ORD-123: item not found

注意,错误消息展示了完整的错误链。你可以从顶层的提交尝试开始,经过订单处理,一直追踪到实际的验证失败。每一层都使用 %w 添加了自己的上下文。

再例如,输入 Bob、ORD-456 和 ITEM-001 后,输出应为:

Order ORD-456 submitted successfully for Bob

亲手试试

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// 读取客户姓名
	scanner.Scan()
	customerName := scanner.Text()
	
	// 读取订单 ID
	scanner.Scan()
	orderID := scanner.Text()
	
	// 读取商品 ID
	scanner.Scan()
	itemID := scanner.Text()
	
	// TODO: 使用输入值调用 SubmitOrder
	// TODO: 如果发生错误,输出:Error: [full error chain]
	// TODO: 如果成功,输出:Order [orderID] submitted successfully for [customerName]
	
	_ = customerName
	_ = orderID
	_ = itemID
}
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: Go 在线编译器