errors - 错误处理
概述
errors 包提供了创建和操作错误的基本功能。
errors 包是什么:
- 📦 错误创建:创建新的错误值
- 🔧 错误包装:包装错误以添加上下文
- 📋 错误检查:检查错误类型和原因
- 🛠️ 错误处理:Go 错误处理的核心包
主要用途:
- 🌐 错误创建:创建自定义错误
- 📧 错误包装:包装错误添加上下文信息
- 🔐 错误检查:使用
errors.Is和errors.As检查错误 - 📊 错误链:处理错误包装链
- 🖼️ 哨兵错误:定义预声明的错误值
- 🔑 错误断言:类型断言和错误比较
重要说明:
- ⚠️ 简单错误:
errors.New创建简单错误 - ⚠️ 格式化错误:
fmt.Errorf创建带格式的错误 - ⚠️ 错误包装:Go 1.13+ 支持
%w包装错误 - ⚠️ 错误链:包装的错误形成链条
- ✅ 标准库支持:Go 标准库提供完整支持
- ✅ 哨兵错误:推荐使用预声明的错误值
- ✅ 错误检查:使用
errors.Is和errors.As
错误处理示例:
// 创建错误
err := errors.New("something went wrong")
// 包装错误
err = fmt.Errorf("failed to process: %w", err)
// 检查错误
if errors.Is(err, ErrNotFound) {
// 处理特定错误
}
// 提取错误
var target *MyError
if errors.As(err, &target) {
// 处理自定义错误类型
}
错误基础
错误接口
error 接口定义:
type error interface {
Error() string
}
说明:
error是 Go 的内置接口- 只有一个方法
Error()返回错误消息 - 任何实现了该方法的类型都满足 error 接口
自定义错误示例:
type MyError struct {
Message string
Code int
}
func (e *MyError) Error() string {
return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}
错误处理模式
基本模式:
result, err := someFunction()
if err != nil {
// 处理错误
return err
}
// 使用 result
错误传播:
func process() error {
err := doSomething()
if err != nil {
return err // 传播错误
}
return nil
}
核心函数
1. New - 创建错误
func New(text string) error
功能:创建一个新的错误值。
参数:
text:错误消息文本
返回值:
error:新的错误值
示例:
err := errors.New("invalid input")
fmt.Println(err) // 输出:invalid input
注意事项:
- ✅ 推荐用于创建简单错误
- ✅ 适合定义哨兵错误(预声明错误)
- ❌ 不包含堆栈信息
- ❌ 不支持格式化
哨兵错误示例:
// 在包级别定义
var ErrNotFound = errors.New("not found")
// 使用
if user == nil {
return ErrNotFound
}
2. Is - 错误匹配
func Is(err, target error) bool
功能:检查 err 是否匹配 target 错误。
参数:
err:要检查的错误target:目标错误(哨兵错误或特定错误)
返回值:
bool:如果匹配返回 true
工作原理:
- 如果
err和target都是 nil,返回 false - 如果
err.Error() == target.Error(),返回 true - 如果
err实现了Is(error) bool方法,调用该方法 - 如果
err是包装错误,解包后继续检查
示例:
// 定义哨兵错误
var ErrNotFound = errors.New("not found")
// 包装错误
err := fmt.Errorf("user lookup failed: %w", ErrNotFound)
// 检查错误
if errors.Is(err, ErrNotFound) {
fmt.Println("Resource not found")
}
自定义 Is 方法:
type MyError struct {
Code int
}
func (e *MyError) Error() string {
return fmt.Sprintf("Error %d", e.Code)
}
func (e *MyError) Is(target error) bool {
if t, ok := target.(*MyError); ok {
return e.Code == t.Code
}
return false
}
3. As - 错误类型断言
func As(err error, target interface{}) bool
功能:从错误链中提取特定类型的错误。
参数:
err:要检查的错误target:指向目标错误类型的指针
返回值:
bool:如果找到匹配类型返回 true
工作原理:
- 如果
err是 nil,返回 false - 如果
err匹配target类型,设置 target 并返回 true - 如果
err是包装错误,解包后继续检查 - 如果
err实现了As(interface{}) bool方法,调用该方法
示例:
type PathError struct {
Path string
Err error
}
func (e *PathError) Error() string {
return fmt.Sprintf("path %s: %v", e.Path, e.Err)
}
// 使用
err := &PathError{Path: "/tmp", Err: errors.New("permission denied")}
var pathErr *PathError
if errors.As(err, &pathErr) {
fmt.Printf("Failed to access path: %s\n", pathErr.Path)
}
注意事项:
- ✅ target 必须是指针类型
- ✅ 支持从错误链中提取
- ❌ target 不能是接口类型(error 除外)
4. Unwrap - 解包错误
func Unwrap(err error) error
功能:解包 err,返回内部包装的错误。
参数:
err:要解包的错误
返回值:
error:内部错误,如果无法解包返回 nil
工作原理:
- 如果
err实现了Unwrap() error方法,调用该方法 - 否则返回 nil
示例:
err1 := errors.New("original error")
err2 := fmt.Errorf("wrapped: %w", err1)
unwrapped := errors.Unwrap(err2)
fmt.Println(unwrapped == err1) // 输出:true
自定义 Unwrap 方法:
type MyError struct {
Err error
}
func (e *MyError) Error() string {
return fmt.Sprintf("MyError: %v", e.Err)
}
func (e *MyError) Unwrap() error {
return e.Err
}
错误包装
fmt.Errorf 包装
基本语法:
err := fmt.Errorf("context: %w", originalErr)
注意事项:
- ✅ 使用
%w包装错误(Go 1.13+) - ✅ 可以使用
%v或%s包含错误但不包装 - ❌
%w只能使用一次 - ❌ 不能包装 nil 错误
示例:
// 包装错误
err := doSomething()
if err != nil {
return fmt.Errorf("process failed: %w", err)
}
// 多重包装
err = fmt.Errorf("level 1: %w",
fmt.Errorf("level 2: %w",
errors.New("base error")))
错误链
错误链结构:
外层错误 (添加上下文)
↓ Unwrap()
中间错误 (添加上下文)
↓ Unwrap()
原始错误 (根本原因)
示例:
// 创建错误链
baseErr := errors.New("database connection failed")
level1 := fmt.Errorf("query failed: %w", baseErr)
level2 := fmt.Errorf("user lookup failed: %w", level1)
// 检查错误链
errors.Is(level2, baseErr) // true
// 提取错误
var target error
if errors.As(level2, &target) {
// 找到第一个匹配的错误
}
完整示例
示例 1:基本错误创建
package main
import (
"errors"
"fmt"
)
// 定义哨兵错误
var (
ErrNotFound = errors.New("not found")
ErrInvalidInput = errors.New("invalid input")
ErrUnauthorized = errors.New("unauthorized")
)
// validateInput 验证输入
func validateInput(input string) error {
if input == "" {
return ErrInvalidInput
}
return nil
}
// findUser 查找用户
func findUser(id int) error {
if id <= 0 {
return ErrNotFound
}
return nil
}
func main() {
fmt.Println("=== 基本错误创建 ===\n")
// 1. 使用哨兵错误
fmt.Println("1. 哨兵错误:")
err := validateInput("")
if err == ErrInvalidInput {
fmt.Printf(" ✓ 捕获预期错误:%v\n", err)
}
err = findUser(-1)
if err == ErrNotFound {
fmt.Printf(" ✓ 捕获预期错误:%v\n", err)
}
// 2. 创建动态错误
fmt.Println("\n2. 动态错误:")
id := -5
err = errors.New(fmt.Sprintf("invalid user ID: %d", id))
fmt.Printf(" 错误消息:%v\n", err)
// 3. 错误比较
fmt.Println("\n3. 错误比较:")
err1 := errors.New("same error")
err2 := errors.New("same error")
err3 := err1
fmt.Printf(" err1 == err2: %v (不同实例)\n", err1 == err2)
fmt.Printf(" err1 == err3: %v (同一实例)\n", err1 == err3)
fmt.Printf(" errors.Is(err1, err2): %v\n", errors.Is(err1, err2))
fmt.Printf(" errors.Is(err1, err3): %v\n", errors.Is(err1, err3))
// 4. 哨兵错误的优势
fmt.Println("\n4. 哨兵错误的优势:")
fmt.Printf(" ErrNotFound == ErrNotFound: %v\n", ErrNotFound == ErrNotFound)
fmt.Printf(" errors.Is(ErrNotFound, ErrNotFound): %v\n",
errors.Is(ErrNotFound, ErrNotFound))
}
输出:
=== 基本错误创建 ===
1. 哨兵错误:
✓ 捕获预期错误:invalid input
✓ 捕获预期错误:not found
2. 动态错误:
错误消息:invalid user ID: -5
3. 错误比较:
err1 == err2: false (不同实例)
err1 == err3: true (同一实例)
errors.Is(err1, err2): false
errors.Is(err1, err3): true
4. 哨兵错误的优势:
ErrNotFound == ErrNotFound: true
errors.Is(ErrNotFound, ErrNotFound): true
示例 2:错误包装和展开
package main
import (
"errors"
"fmt"
)
// 模拟底层操作
func databaseQuery(id int) error {
if id < 0 {
return errors.New("negative ID not allowed")
}
if id == 0 {
return errors.New("ID cannot be zero")
}
return nil
}
// 中间层
func getUserFromDB(id int) error {
err := databaseQuery(id)
if err != nil {
return fmt.Errorf("database query failed: %w", err)
}
return nil
}
// 顶层
func GetUser(id int) error {
err := getUserFromDB(id)
if err != nil {
return fmt.Errorf("GetUser failed: %w", err)
}
return nil
}
func main() {
fmt.Println("=== 错误包装和展开 ===\n")
// 1. 创建错误链
fmt.Println("1. 错误链:")
err := GetUser(-1)
fmt.Printf(" 完整错误:%v\n\n", err)
// 2. 解包错误
fmt.Println("2. 解包错误:")
level1 := err
level2 := errors.Unwrap(level1)
level3 := errors.Unwrap(level2)
fmt.Printf(" 外层:%v\n", level1)
fmt.Printf(" 中间层:%v\n", level2)
fmt.Printf(" 原始错误:%v\n\n", level3)
// 3. 错误匹配
fmt.Println("3. 错误匹配:")
baseErr := errors.New("negative ID not allowed")
fmt.Printf(" errors.Is(err, baseErr): %v\n", errors.Is(err, baseErr))
fmt.Printf(" errors.Is(level1, baseErr): %v\n", errors.Is(level1, baseErr))
fmt.Printf(" errors.Is(level2, baseErr): %v\n", errors.Is(level2, baseErr))
fmt.Printf(" errors.Is(level3, baseErr): %v\n\n", errors.Is(level3, baseErr))
// 4. 遍历错误链
fmt.Println("4. 遍历错误链:")
currentErr := err
depth := 0
for currentErr != nil {
fmt.Printf(" 深度 %d: %v\n", depth, currentErr)
currentErr = errors.Unwrap(currentErr)
depth++
}
// 5. 包装非错误
fmt.Println("\n5. 包装非错误(使用 %v):")
originalErr := errors.New("original")
wrappedWithW := fmt.Errorf("with %%w: %w", originalErr)
wrappedWithV := fmt.Errorf("with %%v: %v", originalErr)
fmt.Printf(" 使用 %%w: %v\n", wrappedWithW)
fmt.Printf(" 使用 %%v: %v\n", wrappedWithV)
fmt.Printf(" errors.Is(wrappedWithW, original): %v\n",
errors.Is(wrappedWithW, originalErr))
fmt.Printf(" errors.Is(wrappedWithV, original): %v\n",
errors.Is(wrappedWithV, originalErr))
}
输出:
=== 错误包装和展开 ===
1. 错误链:
完整错误:GetUser failed: database query failed: negative ID not allowed
2. 解包错误:
外层:GetUser failed: database query failed: negative ID not allowed
中间层:database query failed: negative ID not allowed
原始错误:negative ID not allowed
3. 错误匹配:
errors.Is(err, baseErr): true
errors.Is(level1, baseErr): true
errors.Is(level2, baseErr): true
errors.Is(level3, baseErr): true
4. 遍历错误链:
深度 0: GetUser failed: database query failed: negative ID not allowed
深度 1: database query failed: negative ID not allowed
深度 2: negative ID not allowed
5. 包装非错误(使用 %v):
使用 %w: with %w: original
使用 %v: with %v: original
errors.Is(wrappedWithW, original): true
errors.Is(wrappedWithV, original): false
示例 3:错误类型断言
package main
import (
"errors"
"fmt"
"net"
"os"
)
// CustomError 自定义错误类型
type CustomError struct {
Code string
Message string
}
func (e *CustomError) Error() string {
return fmt.Sprintf("[%s] %s", e.Code, e.Message)
}
// 模拟可能返回不同类型错误的函数
func operationThatFails(failType string) error {
switch failType {
case "custom":
return &CustomError{Code: "E001", Message: "Custom error occurred"}
case "net":
return &net.OpError{Op: "dial", Net: "tcp", Err: errors.New("connection refused")}
case "path":
return &os.PathError{Op: "open", Path: "/tmp/test", Err: errors.New("permission denied")}
default:
return errors.New("unknown error")
}
}
func main() {
fmt.Println("=== 错误类型断言 ===\n")
// 1. 使用 errors.As
fmt.Println("1. 使用 errors.As:")
err := operationThatFails("custom")
var customErr *CustomError
if errors.As(err, &customErr) {
fmt.Printf(" ✓ 提取到 CustomError\n")
fmt.Printf(" Code: %s\n", customErr.Code)
fmt.Printf(" Message: %s\n\n", customErr.Message)
}
// 2. 处理网络错误
fmt.Println("2. 处理网络错误:")
err = operationThatFails("net")
var netErr *net.OpError
if errors.As(err, &netErr) {
fmt.Printf(" ✓ 提取到 OpError\n")
fmt.Printf(" 操作:%s\n", netErr.Op)
fmt.Printf(" 网络:%s\n", netErr.Net)
fmt.Printf(" 错误:%v\n\n", netErr.Err)
}
// 3. 处理路径错误
fmt.Println("3. 处理路径错误:")
err = operationThatFails("path")
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Printf(" ✓ 提取到 PathError\n")
fmt.Printf(" 操作:%s\n", pathErr.Op)
fmt.Printf(" 路径:%s\n", pathErr.Path)
fmt.Printf(" 错误:%v\n\n", pathErr.Err)
}
// 4. 包装后的类型断言
fmt.Println("4. 包装后的类型断言:")
originalErr := &CustomError{Code: "E002", Message: "Original error"}
wrappedErr := fmt.Errorf("context: %w", originalErr)
var extracted *CustomError
if errors.As(wrappedErr, &extracted) {
fmt.Printf(" ✓ 从包装错误中提取\n")
fmt.Printf(" Code: %s\n", extracted.Code)
fmt.Printf(" Message: %s\n\n", extracted.Message)
}
// 5. 类型断言 vs errors.As
fmt.Println("5. 类型断言 vs errors.As:")
err = operationThatFails("custom")
// 传统类型断言(仅适用于无包装)
if customErr, ok := err.(*CustomError); ok {
fmt.Printf(" 类型断言成功:%s\n", customErr.Message)
}
// errors.As(适用于包装错误)
if errors.As(err, &customErr) {
fmt.Printf(" errors.As 成功:%s\n", customErr.Message)
}
}
输出:
=== 错误类型断言 ===
1. 使用 errors.As:
✓ 提取到 CustomError
Code: E001
Message: Custom error occurred
2. 处理网络错误:
✓ 提取到 OpError
操作:dial
网络:tcp
错误:connection refused
3. 处理路径错误:
✓ 提取到 PathError
操作:open
路径:/tmp/test
错误:permission denied
4. 包装后的类型断言:
✓ 从包装错误中提取
Code: E002
Message: Original error
5. 类型断言 vs errors.As:
类型断言成功:Custom error occurred
errors.As 成功:Custom error occurred
示例 4:自定义错误类型
package main
import (
"errors"
"fmt"
)
// APIError API 错误
type APIError struct {
HTTPStatus int `json:"status"`
Code string `json:"code"`
Message string `json:"message"`
Err error `json:"-"`
}
// Error 实现 error 接口
func (e *APIError) Error() string {
if e.Err != nil {
return fmt.Sprintf("%s: %v", e.Message, e.Err)
}
return e.Message
}
// Unwrap 实现 unwrapper 接口
func (e *APIError) Unwrap() error {
return e.Err
}
// Is 实现错误匹配
func (e *APIError) Is(target error) bool {
if t, ok := target.(*APIError); ok {
return e.Code == t.Code
}
return false
}
// 预定义的 API 错误
var (
ErrNotFound = &APIError{HTTPStatus: 404, Code: "NOT_FOUND", Message: "Resource not found"}
ErrUnauthorized = &APIError{HTTPStatus: 401, Code: "UNAUTHORIZED", Message: "Authentication required"}
ErrBadRequest = &APIError{HTTPStatus: 400, Code: "BAD_REQUEST", Message: "Invalid request"}
)
// NewAPIError 创建新的 API 错误
func NewAPIError(status int, code, message string, err error) *APIError {
return &APIError{
HTTPStatus: status,
Code: code,
Message: message,
Err: err,
}
}
// validateRequest 验证请求
func validateRequest(data map[string]interface{}) error {
if data == nil {
return ErrBadRequest
}
if _, ok := data["id"]; !ok {
return NewAPIError(400, "MISSING_ID", "Missing required field: id", nil)
}
return nil
}
// getResource 获取资源
func getResource(id int) error {
if id <= 0 {
return ErrNotFound
}
// 模拟其他错误
return NewAPIError(500, "INTERNAL_ERROR", "Internal server error",
errors.New("database connection failed"))
}
func main() {
fmt.Println("=== 自定义错误类型 ===\n")
// 1. 使用预定义错误
fmt.Println("1. 预定义错误:")
err := validateRequest(nil)
if errors.Is(err, ErrBadRequest) {
fmt.Printf(" ✓ 捕获预期错误:%v\n", err)
}
// 2. 创建带上下文的错误
fmt.Println("\n2. 带上下文的错误:")
err = validateRequest(map[string]interface{}{})
if apiErr, ok := err.(*APIError); ok {
fmt.Printf(" HTTP 状态:%d\n", apiErr.HTTPStatus)
fmt.Printf(" 错误码:%s\n", apiErr.Code)
fmt.Printf(" 消息:%s\n", apiErr.Message)
}
// 3. 包装错误
fmt.Println("\n3. 包装错误:")
err = getResource(-1)
wrappedErr := fmt.Errorf("getResource failed: %w", err)
fmt.Printf(" 包装后:%v\n", wrappedErr)
fmt.Printf(" errors.Is(wrappedErr, ErrNotFound): %v\n",
errors.Is(wrappedErr, ErrNotFound))
// 4. 提取自定义错误
fmt.Println("\n4. 提取自定义错误:")
var apiErr *APIError
if errors.As(wrappedErr, &apiErr) {
fmt.Printf(" ✓ 提取到 APIError\n")
fmt.Printf(" HTTP 状态:%d\n", apiErr.HTTPStatus)
fmt.Printf(" 错误码:%s\n", apiErr.Code)
fmt.Printf(" 消息:%s\n", apiErr.Message)
if apiErr.Err != nil {
fmt.Printf(" 原始错误:%v\n", apiErr.Err)
}
}
// 5. 错误码匹配
fmt.Println("\n5. 错误码匹配:")
err1 := &APIError{Code: "NOT_FOUND", Message: "User not found"}
err2 := &APIError{Code: "NOT_FOUND", Message: "Product not found"}
err3 := &APIError{Code: "BAD_REQUEST", Message: "Invalid input"}
fmt.Printf(" err1 和 err2 同码:%v\n", errors.Is(err1, err2))
fmt.Printf(" err1 和 err3 同码:%v\n", errors.Is(err1, err3))
}
输出:
=== 自定义错误类型 ===
1. 预定义错误:
✓ 捕获预期错误:Invalid request
2. 带上下文的错误:
HTTP 状态:400
错误码:MISSING_ID
消息:Missing required field: id
3. 包装错误:
包装后:getResource failed: Resource not found
errors.Is(wrappedErr, ErrNotFound): true
4. 提取自定义错误:
✓ 提取到 APIError
HTTP 状态:404
错误码:NOT_FOUND
消息:Resource not found
5. 错误码匹配:
err1 和 err2 同码:true
err1 和 err3 同码:false
示例 5:错误处理最佳实践
package main
import (
"errors"
"fmt"
"os"
)
// 定义包级别的哨兵错误
var (
ErrEmptyFile = errors.New("file is empty")
ErrInvalidFormat = errors.New("invalid format")
)
// ConfigError 配置错误
type ConfigError struct {
Field string
Reason string
Err error
}
func (e *ConfigError) Error() string {
return fmt.Sprintf("config field %q: %s (%v)", e.Field, e.Reason, e.Err)
}
func (e *ConfigError) Unwrap() error {
return e.Err
}
// ProcessFile 处理文件
func ProcessFile(filename string) error {
// 检查文件是否存在
if _, err := os.Stat(filename); os.IsNotExist(err) {
return fmt.Errorf("file does not exist: %w", err)
}
// 模拟读取文件
content := ""
if content == "" {
return ErrEmptyFile
}
return nil
}
// LoadConfig 加载配置
func LoadConfig(data map[string]string) error {
if data == nil {
return &ConfigError{
Field: "config",
Reason: "cannot be nil",
Err: errors.New("validation failed"),
}
}
value, ok := data["port"]
if !ok {
return &ConfigError{
Field: "port",
Reason: "is required",
Err: ErrInvalidFormat,
}
}
if value == "" {
return &ConfigError{
Field: "port",
Reason: "cannot be empty",
Err: ErrInvalidFormat,
}
}
return nil
}
func handleError(err error) {
// 1. 检查哨兵错误
if errors.Is(err, ErrEmptyFile) {
fmt.Printf(" 处理:文件为空\n")
return
}
// 2. 检查标准库错误
if os.IsNotExist(err) || errors.Is(err, os.ErrNotExist) {
fmt.Printf(" 处理:文件不存在\n")
return
}
// 3. 提取自定义错误
var configErr *ConfigError
if errors.As(err, &configErr) {
fmt.Printf(" 处理:配置错误 - 字段=%s, 原因=%s\n",
configErr.Field, configErr.Reason)
return
}
// 4. 默认处理
fmt.Printf(" 处理:未知错误 - %v\n", err)
}
func main() {
fmt.Println("=== 错误处理最佳实践 ===\n")
// 1. 处理文件不存在
fmt.Println("1. 文件不存在:")
err := ProcessFile("nonexistent.txt")
handleError(err)
// 2. 处理空文件
fmt.Println("\n2. 空文件:")
err = ProcessFile("empty.txt")
handleError(err)
// 3. 处理配置错误 - nil
fmt.Println("\n3. 配置为 nil:")
err = LoadConfig(nil)
handleError(err)
// 4. 处理配置错误 - 缺失字段
fmt.Println("\n4. 缺失字段:")
err = LoadConfig(map[string]string{})
handleError(err)
// 5. 处理配置错误 - 字段为空
fmt.Println("\n5. 字段为空:")
err = LoadConfig(map[string]string{"port": ""})
handleError(err)
// 6. 成功情况
fmt.Println("\n6. 成功加载:")
err = LoadConfig(map[string]string{"port": "8080"})
if err == nil {
fmt.Printf(" ✓ 配置加载成功\n")
}
// 7. 错误包装链处理
fmt.Println("\n7. 错误包装链:")
baseErr := ErrInvalidFormat
wrapped1 := fmt.Errorf("validation: %w", baseErr)
wrapped2 := fmt.Errorf("config load: %w", wrapped1)
fmt.Printf(" 完整错误链:%v\n", wrapped2)
// 检查原始错误
if errors.Is(wrapped2, ErrInvalidFormat) {
fmt.Printf(" ✓ 可以追溯到原始错误\n")
}
// 提取特定类型
var configErr *ConfigError
if errors.As(wrapped2, &configErr) {
fmt.Printf(" 提取到配置错误\n")
} else {
fmt.Printf(" 未提取到配置错误(预期)\n")
}
}
输出:
=== 错误处理最佳实践 ===
1. 文件不存在:
处理:文件不存在
2. 空文件:
处理:文件为空
3. 配置为 nil:
处理:配置错误 - 字段=config, 原因=cannot be nil
4. 缺失字段:
处理:配置错误 - 字段=port, 原因=is required
5. 字段为空:
处理:配置错误 - 字段=port, 原因=cannot be empty
6. 成功加载:
✓ 配置加载成功
7. 错误包装链:
完整错误链:config load: validation: invalid format
✓ 可以追溯到原始错误
未提取到配置错误(预期)
示例 6:标准库错误辅助函数
package main
import (
"errors"
"fmt"
"os"
)
func main() {
fmt.Println("=== 标准库错误辅助函数 ===\n")
// 1. os.IsNotExist
fmt.Println("1. os.IsNotExist:")
_, err := os.Open("nonexistent_file.txt")
if os.IsNotExist(err) {
fmt.Printf(" ✓ 文件不存在:%v\n", err)
}
// 2. os.IsExist
fmt.Println("\n2. os.IsExist:")
err = os.Mkdir("/existing_dir", 0755)
if os.IsExist(err) {
fmt.Printf(" ✓ 目录已存在:%v\n", err)
}
// 3. os.IsPermission
fmt.Println("\n3. os.IsPermission:")
_, err = os.Open("/root/protected_file")
if os.IsPermission(err) {
fmt.Printf(" ✓ 权限不足:%v\n", err)
}
// 4. 使用 errors.Is 检查标准错误
fmt.Println("\n4. 使用 errors.Is:")
_, err = os.Open("nonexistent.txt")
if errors.Is(err, os.ErrNotExist) {
fmt.Printf(" ✓ errors.Is 检查:文件不存在\n")
}
// 5. 包装后仍然可以识别
fmt.Println("\n5. 包装后的错误检查:")
wrappedErr := fmt.Errorf("open file failed: %w", err)
if errors.Is(wrappedErr, os.ErrNotExist) {
fmt.Printf(" ✓ 包装后仍然可以识别\n")
}
// 6. 标准库错误值
fmt.Println("\n6. 标准库错误值:")
fmt.Printf(" os.ErrNotExist: %v\n", os.ErrNotExist)
fmt.Printf(" os.ErrExist: %v\n", os.ErrExist)
fmt.Printf(" os.ErrPermission: %v\n", os.ErrPermission)
fmt.Printf(" os.ErrClosed: %v\n", os.ErrClosed)
}
输出:
=== 标准库错误辅助函数 ===
1. os.IsNotExist:
✓ 文件不存在:open nonexistent_file.txt: no such file or directory
2. os.IsExist:
✓ 目录已存在:mkdir /existing_dir: file exists
3. os.IsPermission:
✓ 权限不足:open /root/protected_file: permission denied
4. 使用 errors.Is:
✓ errors.Is 检查:文件不存在
5. 包装后的错误检查:
✓ 包装后仍然可以识别
6. 标准库错误值:
os.ErrNotExist: file does not exist
os.ErrExist: file already exists
os.ErrPermission: permission denied
os.ErrClosed: file already closed
示例 7:错误处理模式
package main
import (
"errors"
"fmt"
)
// 模式 1:哨兵错误
var ErrDivideByZero = errors.New("division by zero")
func divide(a, b int) (int, error) {
if b == 0 {
return 0, ErrDivideByZero
}
return a / b, nil
}
// 模式 2:错误包装
func processDivision(a, b int) error {
_, err := divide(a, b)
if err != nil {
return fmt.Errorf("processDivision(%d, %d) failed: %w", a, b, err)
}
return nil
}
// 模式 3:错误检查函数
func checkError(err error) {
if err == nil {
return
}
// 检查特定错误
if errors.Is(err, ErrDivideByZero) {
fmt.Printf(" 错误:除以零\n")
return
}
// 默认处理
fmt.Printf(" 错误:%v\n", err)
}
// 模式 4:错误恢复
func safeDivide(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
if b == 0 {
panic("division by zero")
}
result = a / b
return result, nil
}
// 模式 5:多错误处理
type MultiError []error
func (m MultiError) Error() string {
if len(m) == 0 {
return ""
}
return fmt.Sprintf("%v", []error(m))
}
func (m MultiError) HasError() bool {
return len(m) > 0
}
func validateInputs(inputs ...int) MultiError {
var errs MultiError
for i, input := range inputs {
if input < 0 {
errs = append(errs, fmt.Errorf("input[%d] is negative: %d", i, input))
}
if input == 0 {
errs = append(errs, fmt.Errorf("input[%d] is zero", i))
}
}
return errs
}
func main() {
fmt.Println("=== 错误处理模式 ===\n")
// 1. 哨兵错误模式
fmt.Println("1. 哨兵错误模式:")
result, err := divide(10, 0)
if err != nil {
fmt.Printf(" 结果:%d, 错误:%v\n", result, err)
}
// 2. 错误包装模式
fmt.Println("\n2. 错误包装模式:")
err = processDivision(10, 0)
checkError(err)
// 3. 错误恢复模式
fmt.Println("\n3. 错误恢复模式:")
result, err = safeDivide(10, 0)
if err != nil {
fmt.Printf(" 恢复的错误:%v\n", err)
}
// 4. 多错误处理模式
fmt.Println("\n4. 多错误处理模式:")
errs := validateInputs(10, -5, 0, -3)
if errs.HasError() {
fmt.Printf(" 验证失败,发现 %d 个错误:\n", len(errs))
for _, err := range errs {
fmt.Printf(" - %v\n", err)
}
}
// 5. 成功情况
fmt.Println("\n5. 成功情况:")
result, err = divide(10, 2)
if err == nil {
fmt.Printf(" ✓ 计算成功:%d / %d = %d\n", 10, 2, result)
}
}
输出:
=== 错误处理模式 ===
1. 哨兵错误模式:
结果:0, 错误:division by zero
2. 错误包装模式:
错误:processDivision(10, 0) failed: division by zero
3. 错误恢复模式:
恢复的错误:recovered from panic: division by zero
4. 多错误处理模式:
验证失败,发现 3 个错误:
- input[1] is negative: -5
- input[2] is zero
- input[3] is negative: -3
5. 成功情况:
✓ 计算成功:10 / 2 = 5
最佳实践
✅ 推荐做法
-
使用哨兵错误
// ✅ 推荐 var ErrNotFound = errors.New("not found") if err == ErrNotFound { // 处理 } -
使用 errors.Is 检查错误
// ✅ 推荐 if errors.Is(err, ErrNotFound) { // 处理 } // ❌ 不推荐(不支持包装) if err == ErrNotFound { // 处理 } -
使用 errors.As 提取错误
// ✅ 推荐 var pathErr *os.PathError if errors.As(err, &pathErr) { // 处理 } -
使用 %w 包装错误
// ✅ 推荐 return fmt.Errorf("context: %w", err) // ❌ 不推荐(不支持错误链) return fmt.Errorf("context: %v", err) -
定义有意义的错误消息
// ✅ 推荐 errors.New("user ID must be positive") // ❌ 不推荐 errors.New("error occurred")
❌ 不安全做法
-
不要忽略错误
// ❌ 错误 result, _ := someFunction() // ✅ 正确 result, err := someFunction() if err != nil { return err } -
不要包装 nil 错误
// ❌ 错误 return fmt.Errorf("context: %w", err) // err 可能是 nil // ✅ 正确 if err != nil { return fmt.Errorf("context: %w", err) } return nil -
不要过度包装
// ❌ 错误:过度包装 return fmt.Errorf("layer3: %w", fmt.Errorf("layer2: %w", fmt.Errorf("layer1: %w", err))) // ✅ 正确:适度包装 return fmt.Errorf("operation failed: %w", err)
性能优化
1. 避免不必要的错误包装
// ✅ 推荐:直接返回
if err != nil {
return err
}
// ❌ 不推荐:无意义包装
if err != nil {
return fmt.Errorf("error: %w", err)
}
2. 使用哨兵错误提高性能
// ✅ 推荐:哨兵错误(指针比较)
if err == ErrNotFound {
// 快速路径
}
// ❌ 不推荐:字符串比较
if err.Error() == "not found" {
// 慢
}
总结
核心函数
| 函数 | 用途 | 返回值 |
|---|---|---|
| New | 创建错误 | error |
| Is | 错误匹配 | bool |
| As | 错误断言 | bool |
| Unwrap | 解包错误 | error |
错误包装
| 方法 | 说明 | 示例 |
|---|---|---|
| %w | 包装错误 | fmt.Errorf("ctx: %w", err) |
| %v | 包含错误 | fmt.Errorf("ctx: %v", err) |
| Unwrap() | 解包方法 | func (e *E) Unwrap() error |
| Is() | 匹配方法 | func (e *E) Is(error) bool |
| As() | 断言方法 | func (e *E) As(interface{}) bool |
错误处理模式
| 模式 | 说明 | 用途 |
|---|---|---|
| 哨兵错误 | 预声明错误值 | 特定错误检查 |
| 错误包装 | 添加上下文 | 错误传播 |
| 错误链 | 多层包装 | 追溯根本原因 |
| 类型断言 | 提取类型 | 获取错误详情 |
| 多错误 | 收集多个错误 | 批量验证 |
标准库辅助函数
| 函数 | 用途 |
|---|---|
| os.IsNotExist | 检查文件不存在 |
| os.IsExist | 检查文件已存在 |
| os.IsPermission | 检查权限错误 |
| errors.Is | 通用错误匹配 |
| errors.As | 通用错误断言 |
参考资料
最后更新:2026-04-03
Go 版本:Go 1.23+