使用 `%w` 包装错误
本课属于 Coddy GO 学习旅程的“逻辑与流程”单元。第 39 课,共 68 课。
应用中发生错误时,通常需要在错误沿着代码的不同层级向上传递时补充上下文。Go 提供了一种强大的机制,可以使用 fmt.Errorf 中的 %w 格式动词来包装错误。
错误包装可以在保留底层原始错误的同时,为现有错误添加上下文信息。这样会形成一条错误链,保留问题发生过程的完整记录:
originalErr := errors.New("file not found")
wrappedErr := fmt.Errorf("failed to load config: %w", originalErr)%w 动词很特殊,它会告诉 fmt.Errorf 包装错误,而不只是将其转换为字符串。这样可以保留原始错误,以便之后使用 errors.Is 和 errors.As 之类的函数检查它。
这种模式在大型应用中尤其有用,因为错误可能会经过多个函数,每个函数都会添加自己的上下文。例如,数据库连接错误可能会被包装为 "failed to connect to database",然后再次包装为 "unable to start server",从而清楚地记录发生了什么以及问题出在哪里。
挑战
简单构建一个文件处理系统,在错误经过应用程序的不同层级时,用上下文信息包装错误。本挑战将演示如何使用 fmt.Errorf 中的 %w 格式动词来创建错误链,在保留原始错误的同时添加有意义的上下文。
你会收到两个输入:
- 一个字符串,其中包含文件操作详情,格式为
"filename,operation,status"(例如:"config.json,read,permission_denied") - 一个字符串,其中包含系统上下文,格式为
"service_name,component"(例如:"UserService,ConfigLoader")
你的任务是:
- 通过按逗号拆分第一个输入,获取文件名、操作和状态
- 通过按逗号拆分第二个输入,获取服务名称和组件
- 根据状态使用
errors.New()创建原始错误:- 如果状态是
"permission_denied":"permission denied" - 如果状态是
"not_found":"file not found" - 如果状态是
"corrupted":"file corrupted" - 对于任何其他状态:
"unknown error"
- 如果状态是
- 使用
fmt.Errorf和%w创建文件层级的包装错误:"failed to [operation] file [filename]: %w" - 创建组件层级的包装错误:
"[component] error: %w" - 创建服务层级的包装错误:
"[service_name] operation failed: %w" - 逐层打印错误,以显示错误链:
"Original error: [original_error_message]""File level: [file_level_error_message]""Component level: [component_level_error_message]""Service level: [service_level_error_message]"
- 显示错误链摘要:
"Error Chain Summary:""File: [filename]""Operation: [operation]""Component: [component]""Service: [service_name]""Root cause: [original_error_message]"
使用 strings 包按逗号拆分输入字符串,使用 errors 包创建原始错误,并使用 fmt 包创建包装错误。本挑战将演示使用 %w 包装错误,如何在错误经过应用程序的不同层级时清晰地保留上下文线索,从而让调试更加高效。
亲手试试
package main
import (
"errors"
"fmt"
"strings"
)
func main() {
// 读取输入
var fileDetails string
var systemContext string
fmt.Scanln(&fileDetails)
fmt.Scanln(&systemContext)
// 解析输入
fileInfo := strings.Split(fileDetails, ",")
filename := fileInfo[0]
operation := fileInfo[1]
status := fileInfo[2]
contextInfo := strings.Split(systemContext, ",")
serviceName := contextInfo[0]
component := contextInfo[1]
// TODO: 在下面编写你的代码
// 根据 status 创建原始错误
// 创建文件级包装错误
// 创建组件级包装错误
// 创建服务级包装错误
// 输出每一层错误和摘要
}这节课包含一个小测验。开始学习即可作答,并记录你的进度。
逻辑与流程中的所有课
自己练习: Go 在线编译器