0Pricing
Go Academy · 课时

创建自定义错误

errors.New、fmt.Errorf 与哨兵错误

创建自定义错误 是 CoddyKit 上的免费 Go Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Go Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Go Academy 课程共包含 4 节课。

为什么需要自定义错误类型?

自定义错误类型可以携带字符串消息以外的结构化数据,使调用方能够检查特定错误情况并作出响应:

package main
import "fmt"

// Custom error type carries extra context
type HTTPError struct {
    StatusCode int
    Message    string
}

func (e *HTTPError) Error() string {
    return fmt.Sprintf("HTTP %d: %s", e.StatusCode, e.Message)
}

func fetch(url string) error {
    return &HTTPError{404, "page not found"}
}

func main() {
    err := fetch("http://example.com")
    fmt.Println(err) // HTTP 404: page not found
}

实现 error 接口

任何具有 Error() string 方法的类型都满足 error:

package main
import "fmt"

type ValidationError struct {
    Field   string
    Value   interface{}
    Message string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation: field %q value %v — %s", e.Field, e.Value, e.Message)
}

func validateAge(age int) error {
    if age < 0 || age > 150 {
        return &ValidationError{"age", age, "must be between 0 and 150"}
    }
    return nil
}

func main() {
    fmt.Println(validateAge(-5))
}

使用 errors.New 创建哨兵错误

哨兵错误是包级变量,用于表示广为人知的错误情况:

package main
import ("fmt"; "errors")

var (
    ErrNotFound      = errors.New("not found")
    ErrUnauthorized  = errors.New("unauthorized")
    ErrAlreadyExists = errors.New("already exists")
)

func getUser(id int) (string, error) {
    if id != 1 { return "", ErrNotFound }
    return "Alice", nil
}

func main() {
    _, err := getUser(99)
    if errors.Is(err, ErrNotFound) {
        fmt.Println("user does not exist")
    }
}

使用 %w 的 fmt.Errorf 进行包装

使用带有 %w 的 fmt.Errorf 可以包装错误,同时为 errors.Is 和 errors.As 保留原始错误:

package main
import ("fmt"; "errors")

var ErrDatabase = errors.New("database error")

func queryDB() error {
    return fmt.Errorf("queryDB: %w", ErrDatabase)
}

func main() {
    err := queryDB()
    fmt.Println(err)                       // queryDB: database error
    fmt.Println(errors.Is(err, ErrDatabase)) // true — unwrapped
}

带有 Unwrap 的自定义错误

在自定义错误类型上实现 Unwrap() error,即可参与错误链:

package main
import ("fmt"; "errors")

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

type QueryError struct {
    Query string
    Err   error
}

func (e *QueryError) Error() string {
    return fmt.Sprintf("query %q: %v", e.Query, e.Err)
}
func (e *QueryError) Unwrap() error { return e.Err }

func main() {
    err := &QueryError{"SELECT *", ErrNotFound}
    fmt.Println(err)
    fmt.Println(errors.Is(err, ErrNotFound)) // true
}

errors.Is — 身份检查

errors.Is 会遍历错误链,查找与目标相等的错误:

package main
import ("fmt"; "errors")

var ErrTimeout = errors.New("timeout")

func deepCall() error {
    return fmt.Errorf("layer3: %w", fmt.Errorf("layer2: %w", ErrTimeout))
}

func main() {
    err := deepCall()
    fmt.Println(errors.Is(err, ErrTimeout)) // true — found deep in chain
}

errors.As — 类型提取

errors.As 会在错误链中查找第一个与目标类型匹配的错误,并将其赋值:

package main
import ("fmt"; "errors")

type PermissionError struct{ User string }
func (e *PermissionError) Error() string { return "permission denied for " + e.User }

func action(user string) error {
    return fmt.Errorf("action: %w", &PermissionError{user})
}

func main() {
    err := action("bob")
    var pe *PermissionError
    if errors.As(err, &pe) {
        fmt.Println("denied:", pe.User) // denied: bob
    }
}

多重错误包装(Go 1.20+)

Go 1.20 新增了 errors.Join,以及支持多个 %w 的 fmt.Errorf,用于合并错误:

package main
import ("fmt"; "errors")

func validate(name, email string) error {
    var errs []error
    if name == ""  { errs = append(errs, errors.New("name required")) }
    if email == "" { errs = append(errs, errors.New("email required")) }
    return errors.Join(errs...)
}

func main() {
    err := validate("", "")
    fmt.Println(err)
    // name required
    // email required
}

错误类型命名约定

Go 中错误变量和类型的命名约定:

  • 哨兵错误变量:ErrXxx(例如 ErrNotFound)
  • 自定义错误类型:XxxError(例如 ValidationError)
  • 哨兵错误使用包级变量,包含丰富信息的错误使用指针类型
  • 导出错误,供调用方在 errors.Is / errors.As 中使用

何时使用哪种方法

选择合适的错误处理方法:

  • errors.New — 简单、没有额外数据,可进行哨兵身份检查
  • fmt.Errorf — 带有可选包装的格式化消息
  • 自定义类型 — 调用方需要结构化数据(字段、代码)时使用
  • errors.Join — 汇总多个验证错误

快速检查

哪个函数可以从错误链中提取特定的错误类型?

回顾:自定义错误

总结:

  • 实现 Error() string 以创建自定义错误类型
  • 添加 Unwrap() error 以参与错误链
  • 使用 errors.Is 进行身份检查,使用 errors.As 进行类型提取
  • 哨兵错误:ErrXxx;自定义类型:XxxError
  • errors.Join 会汇总多个错误(Go 1.20+)

常见问题解答

「创建自定义错误」课时是免费的吗?

是的 — 「创建自定义错误」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Go Academy 课程的其余内容,请升级到 CoddyKit PRO。 Go Academy 课程共包含 4 节课。

「创建自定义错误」这节课中我会学到什么?

errors.New、fmt.Errorf 与哨兵错误 你通过在浏览器中直接运行的动手代码来练习 Go Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Go Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Go Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「创建自定义错误」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Go Academy 课中编写并运行代码吗?

能。每节 Go Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. error 接口
  2. 创建自定义错误
  3. 错误包装与拆包
  4. panic、recover 与 defer
← 返回 Go Academy