Menu
CoddyTech

使用 `%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",从而清楚地记录发生了什么以及问题出在哪里。

challenge icon

挑战

简单

构建一个文件处理系统,在错误经过应用程序的不同层级时,用上下文信息包装错误。本挑战将演示如何使用 fmt.Errorf 中的 %w 格式动词来创建错误链,在保留原始错误的同时添加有意义的上下文。

你会收到两个输入:

  • 一个字符串,其中包含文件操作详情,格式为 "filename,operation,status"(例如:"config.json,read,permission_denied")
  • 一个字符串,其中包含系统上下文,格式为 "service_name,component"(例如:"UserService,ConfigLoader")

你的任务是:

  1. 通过按逗号拆分第一个输入,获取文件名、操作和状态
  2. 通过按逗号拆分第二个输入,获取服务名称和组件
  3. 根据状态使用 errors.New() 创建原始错误:
    • 如果状态是 "permission_denied":"permission denied"
    • 如果状态是 "not_found":"file not found"
    • 如果状态是 "corrupted":"file corrupted"
    • 对于任何其他状态:"unknown error"
  4. 使用 fmt.Errorf 和 %w 创建文件层级的包装错误:"failed to [operation] file [filename]: %w"
  5. 创建组件层级的包装错误:"[component] error: %w"
  6. 创建服务层级的包装错误:"[service_name] operation failed: %w"
  7. 逐层打印错误,以显示错误链:
    • "Original error: [original_error_message]"
    • "File level: [file_level_error_message]"
    • "Component level: [component_level_error_message]"
    • "Service level: [service_level_error_message]"
  8. 显示错误链摘要:
    • "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 创建原始错误
	// 创建文件级包装错误
	// 创建组件级包装错误
	// 创建服务级包装错误
	// 输出每一层错误和摘要
	
}
quiz icon自我测试

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

逻辑与流程中的所有课

自己练习: Go 在线编译器