GoClub Pro

Go-err

最近更新:2026-10-24
本页的笔记正文在原笔记的附件文档里,已全部转成网页全文放在下面,在线就能看;原始笔记文件见「Go语言相关笔记」页的原始链接。

附件文档全文(errors )#

errors包讲解

你想深入了解 Go 语言中 error 相关的核心机制和最佳实践,我会从基础概念到高级用法,全面且易懂地讲解 error 包及 Go 错误处理体系。

一、Go 错误处理的核心思想

Go 没有像 Java/C++ 那样的 try/catch 异常机制,而是采用显式返回错误的设计哲学:

  • 错误是一等公民,通常作为函数最后一个返回值
  • 调用者必须主动检查错误,无法忽略(编译器不会强制,但编程规范要求)
  • 核心是 error 接口,而非单一的 error 包(标准库中实际是 errors 包,注意拼写)

二、基础:error 接口(核心)

error 不是一个包,而是 Go 内置的一个极简接口,定义在 builtin 包中:

Go
// 内置的 error 接口,所有错误类型都需实现该接口
type error interface {
Error() string // 返回错误描述字符串
}

只要实现了 Error() string 方法的类型,都可以作为 error 使用。

三、标准库 errors 包(核心工具)

Go 标准库 errors 包(import "errors")提供了创建和处理错误的基础工具,核心功能如下:

1. 最常用:创建基础错误(errors.New)

Go
package main

import (
"errors"
"fmt"
)

// 定义一个返回错误的函数
func divide(a, b int) (int, error) {
if b == 0 {
// 创建基础错误:返回一个实现了 error 接口的字符串错误
return 0, errors.New("除数不能为 0")
}
return a / b, nil // 无错误时返回 nil
}

func main() {
res, err := divide(10, 0)
// 显式检查错误(Go 错误处理的核心规范)
if err != nil {
fmt.Println("执行失败:", err) // 输出:执行失败:除数不能为 0
return
}
fmt.Println("结果:", res)
}

  • errors.New(s string) error:创建一个简单的错误实例,底层是 errors 包的 errorString 结构体(私有)
  • 特点:轻量、仅包含字符串描述,适合简单场景

2. 进阶:格式化错误(fmt.Errorf)

fmt 包的 Errorf 是 errors.New 的增强版,支持格式化字符串:

Go
func divide(a, b int) (int, error) {
if b == 0 {
// 格式化错误信息,更灵活
return 0, fmt.Errorf("除法错误:%d / %d 中除数为 0", a, b)
}
return a / b, nil
}

func main() {
_, err := divide(10, 0)
if err != nil {
fmt.Println(err) // 输出:除法错误:10 / 0 中除数为 0
}
}

  • Go 1.13+ 新增:%w 动词支持包装错误(错误链),是错误处理的重要升级

3. Go 1.13+ 错误包装与解包(关键特性)

实际开发中,错误往往需要携带上下文(如底层错误原因),%w + errors 包的 Unwrap/Is/As 解决这个问题:

Go
package main

import (
"errors"
"fmt"
)

// 定义基础错误
var ErrZeroDivisor = errors.New("除数为 0")

func divide(a, b int) (int, error) {
if b == 0 {
// 包装基础错误,添加上下文
return 0, fmt.Errorf("执行除法失败:%w", ErrZeroDivisor)
}
return a / b, nil
}

func main() {
res, err := divide(10, 0)
if err != nil {
// 1. errors.Is:判断错误链中是否包含指定错误
if errors.Is(err, ErrZeroDivisor) {
fmt.Println("核心错误:除数不能为 0")
}

// 2. errors.Unwrap:解包获取底层错误
unwrappedErr := errors.Unwrap(err)
fmt.Println("解包后的错误:", unwrappedErr) // 输出:除数为 0

// 3. 直接打印包装后的错误(包含上下文)
fmt.Println("完整错误信息:", err) // 输出:执行除法失败:除数为 0
return
}
fmt.Println("结果:", res)
}

4. 自定义错误类型(复杂场景)

当错误需要携带更多信息(如错误码、堆栈、时间),可以自定义实现 error 接口的结构体:

Go
package main

import (
"errors"
"fmt"
"time"
)

// 自定义错误类型:携带错误码、描述、发生时间
type MyError struct {
Code int // 错误码
Msg string // 错误描述
Time time.Time // 发生时间
Err error // 底层错误(可选,用于错误链)
}

// 实现 error 接口的 Error() 方法
func (e *MyError) Error() string {
return fmt.Sprintf("[%s] 错误码 %d:%s", e.Time.Format("2006-01-02 15:04:05"), e.Code, e.Msg)
}

// 实现 Unwrap 方法(Go 1.13+ 规范),支持 errors.Unwrap/Is/As
func (e *MyError) Unwrap() error {
return e.Err
}

// 模拟业务函数
func doBusiness(id int) error {
if id <= 0 {
baseErr := errors.New("ID 非法")
return &MyError{
Code: 400,
Msg: fmt.Sprintf("ID %d 必须大于 0", id),
Time: time.Now(),
Err: baseErr, // 关联底层错误
}
}
return nil
}

func main() {
err := doBusiness(-1)
if err != nil {
fmt.Println("错误信息:", err)

// errors.As:类型断言,提取自定义错误
var myErr *MyError
if errors.As(err, &myErr) {
fmt.Printf("错误码:%d,发生时间:%s\n", myErr.Code, myErr.Time)
}
}
}

输出示例:

Plain Text
错误信息:[2026-01-25 10:00:00] 错误码 400:ID -1 必须大于 0
错误码:400,发生时间:2026-01-25 10:00:00 +0800 CST

四、错误处理的最佳实践

  1. 不要忽略错误:即使是你认为“不可能失败”的函数,也要检查 err(如文件操作、网络请求)
  2. 错误信息要具体:包含上下文(如“读取文件 config.json 失败:权限不足”,而非“读取失败”)
  3. 分层处理错误:
  • 底层函数:返回原始错误(如 ErrZeroDivisor)
  • 上层函数:包装错误(添加上下文,如 fmt.Errorf("处理用户 %d 数据失败:%w", uid, err))
  • 业务入口:统一处理/记录错误(如打印日志、返回给前端)
  1. 预定义错误变量:对于通用错误(如参数非法、资源不存在),定义包级变量(var ErrInvalidParam = errors.New("参数非法")),方便调用方用 errors.Is 判断
  2. 避免过度包装:不要多层嵌套无意义的上下文,保持错误链简洁

总结

  1. 核心基础:error 是内置接口(仅需实现 Error() string),errors 包是创建/处理错误的核心工具
  2. 核心工具:
  • errors.New:创建简单字符串错误
  • fmt.Errorf("%w"):包装错误(Go 1.13+)
  • errors.Is/As/Unwrap:处理错误链(判断类型、解包底层错误)
  1. 最佳实践:显式检查错误、错误信息具体化、分层包装/处理、预定义通用错误变量。

|(注:文档部分内容可能由 AI 生成)

附件原文:onenote-go-err-errors_.docx(点击下载)

内容反馈

发现错误或想补充内容?

GoClub Pro 是静态知识库,评论功能不再加载第三方脚本。你可以在 GitHub 仓库提交 Issue,反馈会直接进入项目协作流程。

提交 GitHub 反馈