2026-06-07 11:08:10 +08:00
|
|
|
|
---
|
2026-06-07 12:14:39 +08:00
|
|
|
|
tags: [go, golang, go基础语法, error]
|
|
|
|
|
|
create time: 2026-06-07 15:00
|
2026-06-07 11:08:10 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
# Go 语言 error
|
|
|
|
|
|
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
|
|
|
|
|
|
`error` 是 Go 的内置接口(只有一个 `Error() string` 方法)。本文讲解 error 的本质、创建方式、自定义 error、错误包装与比较。
|
|
|
|
|
|
|
|
|
|
|
|
## 正文
|
|
|
|
|
|
|
|
|
|
|
|
### error 到底是什么?
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
type error interface {
|
|
|
|
|
|
Error() string
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
**error 就是一个接口**。任何实现了 `Error() string` 方法的类型都可以作为 error 返回。这意味着你可以完全自定义错误类型。
|
|
|
|
|
|
|
|
|
|
|
|
> [!question] ❓ 思考
|
|
|
|
|
|
> 既然 error 只是一个接口,那为什么不能直接用 `==` 比较两个 error?
|
|
|
|
|
|
|
|
|
|
|
|
### 创建 error 的两种方式
|
|
|
|
|
|
|
|
|
|
|
|
#### errors.New() — 简单错误
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
err := errors.New("something went wrong")
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
底层实现:
|
|
|
|
|
|
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```go
|
|
|
|
|
|
func New(text string) error {
|
2026-06-07 12:14:39 +08:00
|
|
|
|
return &errorString{text} // 返回指针
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
2026-06-07 12:14:39 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
#### fmt.Errorf() — 带格式化的错误
|
|
|
|
|
|
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
err := fmt.Errorf("failed to open file %s: %v", path, innerErr)
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```
|
2026-06-07 12:14:39 +08:00
|
|
|
|
|
|
|
|
|
|
支持 `%w` 包装错误(见下文"错误包装"章节)。
|
|
|
|
|
|
|
|
|
|
|
|
> [!warning] ⚠️ error 不能用 == 直接比较
|
|
|
|
|
|
> `errors.New("hello") == errors.New("hello")` 结果是 `false`,因为它们是不同的指针实例。需要比较字符串内容:`err.Error() == "hello"`。
|
|
|
|
|
|
|
|
|
|
|
|
### 标准错误处理模式
|
|
|
|
|
|
|
|
|
|
|
|
Go 约定:**error 作为最后一个返回值**:
|
|
|
|
|
|
|
2026-06-07 11:08:10 +08:00
|
|
|
|
```go
|
2026-06-07 12:14:39 +08:00
|
|
|
|
func ReadFile(name string) (string, error) {
|
|
|
|
|
|
data, err := os.ReadFile(name)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return "", err // 向上层传递错误
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
2026-06-07 12:14:39 +08:00
|
|
|
|
return string(data), nil
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 调用方
|
|
|
|
|
|
content, err := ReadFile("config.json")
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
log.Fatal(err)
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!tip] 💡 防御性编程
|
|
|
|
|
|
> ```go
|
|
|
|
|
|
> if err != nil {
|
|
|
|
|
|
> return err // 尽早返回,避免嵌套
|
|
|
|
|
|
> }
|
|
|
|
|
|
> // 下面是正常逻辑
|
|
|
|
|
|
> ```
|
|
|
|
|
|
> 这就是著名的 "error laddering"——每个错误处理块都缩进一级,最终正常逻辑在最外层。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 自定义 error
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
实现 `Error() string` 方法即可:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
```go
|
|
|
|
|
|
type NotFoundError struct {
|
|
|
|
|
|
Resource string
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
func (e *NotFoundError) Error() string {
|
|
|
|
|
|
return fmt.Sprintf("resource not found: %s", e.Resource)
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 使用
|
|
|
|
|
|
err := &NotFoundError{Resource: "user_42"}
|
|
|
|
|
|
fmt.Println(err) // resource not found: user_42
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
如果需要携带额外信息(如错误码):
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
type AppError struct {
|
|
|
|
|
|
Code int
|
|
|
|
|
|
Msg string
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
func (e *AppError) Error() string {
|
|
|
|
|
|
return fmt.Sprintf("[%d] %s", e.Code, e.Msg)
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 提取错误码
|
|
|
|
|
|
func ErrorCode(err error) int {
|
|
|
|
|
|
if appErr, ok := err.(*AppError); ok {
|
|
|
|
|
|
return appErr.Code
|
|
|
|
|
|
}
|
|
|
|
|
|
return -1
|
2026-06-07 11:08:10 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!note] 📝 用类型断言提取信息
|
|
|
|
|
|
> `err.(*AppError)` 将 error 接口还原为具体类型,可以访问额外的字段。
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 错误包装(Go 1.13+)
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
`fmt.Errorf` 支持 `%w` 来**包装**错误,保留原始错误链:
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
```go
|
|
|
|
|
|
func LoadConfig(path string) error {
|
|
|
|
|
|
data, err := os.ReadFile(path)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return fmt.Errorf("load config: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
// 检查是否包含特定错误
|
|
|
|
|
|
var fsErr *fs.PathError
|
|
|
|
|
|
if errors.As(err, &fsErr) {
|
|
|
|
|
|
fmt.Printf("文件系统错误: %v\n", fsErr)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查错误链中是否有某个错误
|
|
|
|
|
|
if errors.Is(err, io.EOF) {
|
|
|
|
|
|
fmt.Println("到达文件末尾")
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> [!info] ℹ️ 三个核心函数
|
|
|
|
|
|
> | 函数 | 用途 |
|
|
|
|
|
|
> |------|------|
|
|
|
|
|
|
> | `errors.Is(err, target)` | 检查 err 是否在错误链中等于 target |
|
|
|
|
|
|
> | `errors.As(err, target)` | 将 err 链中的第一个匹配类型赋值给 target |
|
|
|
|
|
|
> | `fmt.Errorf("%w", err)` | 包装错误,保留原始错误 |
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
graph TD
|
|
|
|
|
|
A["load config 错误"] --> B["os.Open 错误"]
|
|
|
|
|
|
B --> C["permission denied"]
|
|
|
|
|
|
|
|
|
|
|
|
style A fill:#f9f,stroke:#333
|
|
|
|
|
|
style B fill:#bbf,stroke:#333
|
|
|
|
|
|
style C fill:#ff9,stroke:#333
|
|
|
|
|
|
```
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
> [!warning] ⚠️ %w vs %v
|
|
|
|
|
|
> - `%w` — **包装**错误,可以用 `errors.Is/As` 追溯
|
|
|
|
|
|
> - `%v` — 仅格式化输出,不保留错误链关系
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
### 最佳实践总结
|
2026-06-07 11:08:10 +08:00
|
|
|
|
|
2026-06-07 12:14:39 +08:00
|
|
|
|
| 场景 | 推荐做法 |
|
|
|
|
|
|
|------|---------|
|
|
|
|
|
|
| 简单错误 | `errors.New()` |
|
|
|
|
|
|
| 带上下文的错误 | `fmt.Errorf("context: %w", err)` |
|
|
|
|
|
|
| 需要提取错误码 | 自定义 struct + `Error() string` |
|
|
|
|
|
|
| 检查错误类型 | `errors.As(err, &target)` |
|
|
|
|
|
|
| 判断是否某类错误 | `errors.Is(err, target)` |
|
|
|
|
|
|
| 业务逻辑错误 | 定义专门的错误类型(如 `ErrNotFound`) |
|