创建自定义错误
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 反馈 — 无需本地设置。