)
前言Go 语言抛弃了传统 Java/Python 的try-catch异常捕获模型采用值返回式错误error作为主流方案搭配panic处理程序不可预期的崩溃、log.Fatal处理初始化致命故障。一、error 标准错误体系error本质是 Go 内置接口仅定义一个方法Error() string任何实现该方法的类型都是合法错误对象。1. error 的两种创建方式1errors.New(文本)基础错误创建标准库errors包内部定义小写非导出结构体errorString首字母小写意味着外部业务包无法直接手动构造该结构体只能通过导出函数New()间接生成错误实例。package main import errors func main() { // 合法创建方式 err : errors.New(文件读取失败) // 以下代码编译报错errorString 是非导出类型外部不可访问 // err : errors.errorString{s: 文件读取失败} }该方式仅能存储一段错误文本无法携带业务字段错误码、路径等适合简单通用错误。2fmt.Errorf()格式化错误支持链式包装分为两种格式化动词行为完全不同%s仅拼接字符串原始错误对象丢失无法溯源%w专用错误包装动词底层生成私有wrapError结构体存储新增描述与原始错误自动实现Unwrap()方法形成链式错误链表package main import ( errors fmt ) func main() { rawErr : errors.New(文件不存在) // %s 仅拼接文字丢失原始错误 err1 : fmt.Errorf(加载配置失败%s, rawErr.Error()) // %w 包装原始错误保留完整错误链 err2 : fmt.Errorf(加载配置失败%w, rawErr) }2. 自定义错误带业务字段的结构体错误当基础错误无法承载业务信息错误码、文件路径、请求 ID时我们自定义结构体实现error接口复刻标准库错误的设计思路拓展自定义字段。package main import fmt // 大写导出结构体外部包可识别、断言匹配 type FileOperateError struct { FileName string // 出错文件路径 Code int // 业务自定义错误码 } // 指针接收者实现 error 接口仅 *FileOperateError 是合法error func (e *FileOperateError) Error() string { return fmt.Sprintf(文件操作异常路径%s错误码%d, e.FileName, e.Code) } // 模拟构造函数类比 errors.New func NewFileErr(name string, code int) error { return FileOperateError{ FileName: name, Code: code, } } func main() { // 创建自定义错误 取地址生成指针实例 err : NewFileErr(data.txt, 5001) // 使用 %w 多层包装形成错误链 wrapErr : fmt.Errorf(读取数据失败%w, err) }关键规则结构体、字段首字母大写外部包可访问、匹配小写仅本包可用指针接收者(e *FileOperateError)仅指针实例实现error接口必须用结构体{}创建错误3. 错误传递多层函数链式包装业务开发典型场景底层函数报错中层函数无需处理补充上下文后向上返回这个过程叫错误传递。单纯拼接字符串会丢失底层原始错误使用%w包装可完整保留整条错误链路func ReadFile(path string) error { // 底层抛出自定义错误 return fmt.Errorf(打开文件%s失败%w, path, NewFileErr(path, 5001)) } func LoadConfig() error { err : ReadFile(config.json) if err ! nil { // 传递错误追加上层上下文 return fmt.Errorf(初始化配置失败%w, err) } return nil }包装后底层私有wrapError结构体持有原始错误依靠Unwrap()逐层解包溯源。4. 错误处理错误链判断errors.Is/errors.As链式错误不能直接用或普通类型断言判断标准库提供两个专用工具函数各司其职①errors.Is(err, targetErr)匹配原始哨兵错误实例判断逻辑遍历错误链对比内存地址是否为同一个原始错误对象不对比错误文本内容。适用场景全局哨兵错误如os.ErrNotExist、自定义全局ErrUserNotFoundpackage main import ( errors fmt ) // 全局哨兵错误全局唯一实例 var ErrFileMissing errors.New(文件不存在) func ReadFile() error { return fmt.Errorf(读取文件失败%w, ErrFileMissing) } func main() { err : ReadFile() // 逐层解包匹配原始哨兵错误 if errors.Is(err, ErrFileMissing) { fmt.Println(捕获到文件缺失错误执行自动创建逻辑) } }避坑两段文字完全一致、但分别errors.New()创建的错误地址不同Is判断返回 false。②errors.As(err, targetPtr)匹配自定义结构体错误类型判断逻辑遍历错误链严格匹配结构体指针类型与结构体内部字段内容无关。必须声明对应指针空变量作为容器函数内部自动填充匹配到的错误实例func main() { err : LoadConfig() // 定义对应结构体指针容器零值nil仅用于指定匹配类型 var fileErr *FileOperateError if errors.As(err, fileErr) { // 匹配成功读取自定义业务字段 fmt.Printf(出错文件%s错误码%d, fileErr.FileName, fileErr.Code) } }硬性约束容器类型必须和错误链内原始错误类型完全一致指针 / 值不能混用。二、panic 运行时恐慌机制error处理可预期、可恢复的业务错误panic用于程序不可预期、严重 bug数组越界、nil 指针、非法内部参数触发后停止当前逻辑逐层向上抛出崩溃信号。1. panic 的两种创建方式手动主动触发panic(非法参数)/panic(err)运行时自动触发切片越界、nil 指针调用方法、类型断言失败等系统错误func test() { num : []int{1, 2} fmt.Println(num[10]) // 数组越界自动panic }2. panic 善后defer 延迟执行panic 向上冒泡过程中会优先执行当前函数所有defer延迟函数可用于资源清理关闭文件、释放连接保证崩溃前完成善后操作func ReadFileSafe() { f, _ : os.Open(data.txt) // 无论函数正常退出还是panic都会关闭文件 defer f.Close() // 触发恐慌 panic(读取文件内存溢出) }3. panic 恢复recover () 捕获崩溃recover()仅能在defer函数内部调用作用是捕获当前协程的 panic阻断程序整体崩溃实现局部容错。适用场景协程内部逻辑出错不影响主服务继续运行。func SafeRun() { defer func() { // 捕获panic if r : recover(); r ! nil { fmt.Printf(捕获崩溃错误信息%v, r) } }() // 会被recover捕获程序不会退出 panic(内部逻辑异常) }限制recover仅能捕获当前 goroutine的 panic子协程崩溃无法在主协程恢复。三、fatal 致命退出log.Fatal/log.Fatalflog.Fatal不属于错误接口体系是程序终止工具底层逻辑打印日志 调用os.Exit(1)直接结束进程。核心特点进程直接退出defer 不会执行、recover 无法捕获无任何恢复空间适用场景程序初始化阶段致命故障配置文件缺失、数据库连接失败程序完全无法启动禁止场景业务运行时常规错误一旦调用会直接杀死整个服务进程package main import log func main() { // 初始化数据库失败程序无法运行直接退出 dbErr : errors.New(数据库连接超时) if dbErr ! nil { log.Fatalf(启动失败%v, dbErr) } }四、error /panic/fatal 使用边界规范优先使用 error业务常规可恢复错误参数错误、文件不存在、请求失败搭配%w链式包装、Is/As做差异化处理谨慎使用 panic仅用于程序内部不可预期 bug禁止用来处理正常业务错误配合defer recover实现局部容错仅初始化场景使用 log.Fatal程序启动阶段致命故障无任何继续运行可能性时使用。五、全文总结error是 Go 标准错误模型分基础错误、自定义结构体错误依靠%w实现错误链式传递错误溯源依靠errors.Is匹配原始哨兵实例、errors.As匹配自定义结构体类型禁止通过错误文本判断业务分支panic处理程序异常崩溃defer做资源清理recover局部恢复log.Fatal直接终止进程仅限程序初始化致命场景使用。遵循这套规范编写错误处理代码能大幅提升项目可读性、可维护性避免线上故障无法溯源、进程无故崩溃等问题。