Menu
CoddyTech

使用 `errors.Is` 解包错误

本课属于 Coddy GO 学习旅程的“逻辑与流程”单元。第 40 课,共 68 课。

处理包装后的错误时,你需要一种方法来检查错误链中的任意位置是否存在特定错误。errors.Is 函数正好提供了这种能力,即使哨兵错误被多次包装,你也能识别出来。

errors.Is 函数接受两个参数:你想检查的错误,以及你要查找的目标错误。如果在错误链中的任意位置找到目标错误,它就会返回 true:

var ErrNotFound = errors.New("not found")

wrappedErr := fmt.Errorf("failed to load user: %w", ErrNotFound)

if errors.Is(wrappedErr, ErrNotFound) {
    fmt.Println("This is a not found error")
}

这是在 Go 中检查特定错误的现代推荐方式。与字符串比较或直接进行相等性检查不同,errors.Is 可以正确处理包装后的错误,并沿着错误链查找匹配项。这样一来,即使错误经过应用程序的多个层级,你的错误处理也能保持健壮可靠。

challenge icon

挑战

简单

构建一个数据库连接监控系统,使用 errors.Is 在包装后的错误链中识别特定的连接错误。本挑战演示如何使用 errors.Is 检测哨兵错误,即使这些错误经过多个应用层并附加了额外上下文,也能识别出来。

你会收到两个输入:

  • 一个包含数据库操作详情的字符串,格式为 "database_name,operation,error_type"(例如:"users_db,connect,connection_timeout")
  • 一个包含应用上下文的字符串,格式为 "service_name,retry_count"(例如:"AuthService,3")

你的任务是:

  1. 使用 errors.New() 定义三个哨兵错误:
    • 定义 ErrConnectionTimeout,错误消息为 "connection timeout"
    • 定义 ErrConnectionRefused,错误消息为 "connection refused"
    • 定义 ErrDatabaseNotFound,错误消息为 "database not found"
  2. 按逗号拆分第一个输入,获取数据库名称、操作和错误类型
  3. 按逗号拆分第二个输入,获取服务名称和重试次数
  4. 根据错误类型创建原始错误:
    • 如果错误类型是 "connection_timeout":使用 ErrConnectionTimeout
    • 如果错误类型是 "connection_refused":使用 ErrConnectionRefused
    • 如果错误类型是 "database_not_found":使用 ErrDatabaseNotFound
    • 对于其他错误类型:使用 errors.New("unknown database error")
  5. 使用 fmt.Errorf 和 %w 创建数据库层的包装错误:"failed to [operation] database [database_name]: %w"
  6. 创建服务层的包装错误:"[service_name] database operation failed after [retry_count] retries: %w"
  7. 使用 errors.Is 检查最终的包装错误是否与每个哨兵错误匹配,并显示结果:
    • "Checking for connection timeout: [true/false]"
    • "Checking for connection refused: [true/false]"
    • "Checking for database not found: [true/false]"
  8. 显示错误链:
    • "Error Chain:"
    • "Original: [original_error_message]"
    • "Database level: [database_level_error_message]"
    • "Service level: [service_level_error_message]"
  9. 显示错误分析:
    • "Error Analysis:"
    • "Database: [database_name]"
    • "Operation: [operation]"
    • "Service: [service_name]"
    • "Retries: [retry_count]"
    • "Error type detected: [detected_error_type]",其中 detected_error_type 是 "Connection Timeout"、"Connection Refused"、"Database Not Found" 或 "Unknown Error" 之一

使用 strings 包按逗号拆分输入字符串,使用 errors 包处理哨兵错误和 errors.Is,并使用 fmt 包包装错误。本挑战演示了 errors.Is 如何可靠地识别错误,并能正确处理包装后的错误,因此它对于复杂应用中的可靠错误处理至关重要。

亲手试试

package main

import (
	"errors"
	"fmt"
	"strings"
)

func main() {
	// 读取输入
	var dbOperation string
	var appContext string
	fmt.Scanln(&dbOperation)
	fmt.Scanln(&appContext)
	
	// 解析输入
	dbParts := strings.Split(dbOperation, ",")
	databaseName := dbParts[0]
	operation := dbParts[1]
	errorType := dbParts[2]
	
	contextParts := strings.Split(appContext, ",")
	serviceName := contextParts[0]
	retryCount := contextParts[1]
	
	// TODO: 在下方编写你的代码
	// 1. 使用 errors.New() 定义三个哨兵错误
	// 2. 根据错误类型创建原始错误
	// 3. 使用 fmt.Errorf 和 %w 创建数据库级包装错误
	// 4. 创建服务级包装错误
	// 5. 使用 errors.Is 检查每个哨兵错误
	// 6. 显示结果和错误链
	
}
quiz icon自我测试

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

逻辑与流程中的所有课

自己练习: Go 在线编译器