使用 `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 可以正确处理包装后的错误,并沿着错误链查找匹配项。这样一来,即使错误经过应用程序的多个层级,你的错误处理也能保持健壮可靠。
挑战
简单构建一个数据库连接监控系统,使用 errors.Is 在包装后的错误链中识别特定的连接错误。本挑战演示如何使用 errors.Is 检测哨兵错误,即使这些错误经过多个应用层并附加了额外上下文,也能识别出来。
你会收到两个输入:
- 一个包含数据库操作详情的字符串,格式为
"database_name,operation,error_type"(例如:"users_db,connect,connection_timeout") - 一个包含应用上下文的字符串,格式为
"service_name,retry_count"(例如:"AuthService,3")
你的任务是:
- 使用
errors.New()定义三个哨兵错误:- 定义
ErrConnectionTimeout,错误消息为"connection timeout" - 定义
ErrConnectionRefused,错误消息为"connection refused" - 定义
ErrDatabaseNotFound,错误消息为"database not found"
- 定义
- 按逗号拆分第一个输入,获取数据库名称、操作和错误类型
- 按逗号拆分第二个输入,获取服务名称和重试次数
- 根据错误类型创建原始错误:
- 如果错误类型是
"connection_timeout":使用ErrConnectionTimeout - 如果错误类型是
"connection_refused":使用ErrConnectionRefused - 如果错误类型是
"database_not_found":使用ErrDatabaseNotFound - 对于其他错误类型:使用
errors.New("unknown database error")
- 如果错误类型是
- 使用
fmt.Errorf和%w创建数据库层的包装错误:"failed to [operation] database [database_name]: %w" - 创建服务层的包装错误:
"[service_name] database operation failed after [retry_count] retries: %w" - 使用
errors.Is检查最终的包装错误是否与每个哨兵错误匹配,并显示结果:"Checking for connection timeout: [true/false]""Checking for connection refused: [true/false]""Checking for database not found: [true/false]"
- 显示错误链:
"Error Chain:""Original: [original_error_message]""Database level: [database_level_error_message]""Service level: [service_level_error_message]"
- 显示错误分析:
"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. 显示结果和错误链
}这节课包含一个小测验。开始学习即可作答,并记录你的进度。
逻辑与流程中的所有课
自己练习: Go 在线编译器