错误包装(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() 检查其中的内容。
挑战
简单我们来构建一个订单处理系统,演示如何在多个层级中包装错误。你将创建一系列函数,每一层都会为错误添加上下文,方便你准确追踪问题发生的位置。
你将把代码分成两个文件:
orders.go:创建分层的订单处理逻辑,在错误向上层传递时逐层包装错误。实现以下三个函数,组成处理链:
ValidateItem(itemID string) error- 如果 itemID 是"INVALID",则返回错误,错误消息为"item not found";否则返回nilProcessOrder(orderID, itemID string) error- 调用ValidateItem。如果它返回错误,就用以下上下文包装错误:"processing order [orderID]: %w"。否则返回nilSubmitOrder(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
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Go 在线编译器