Go 错误处理
Go 语言采用了独特的错误处理机制,通过显式的错误返回值来处理错误情况。这种方式让错误处理变得明确和可预测。
📋 错误处理基础
error 接口
go
package main
import (
"errors"
"fmt"
)
// Go 内置的 error 接口
// type error interface {
// Error() string
// }
// 基本错误处理
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为零")
}
return a / b, nil
}
// 多种错误情况
func validateAge(age int) error {
if age < 0 {
return errors.New("年龄不能为负数")
}
if age > 150 {
return errors.New("年龄不能超过150岁")
}
return nil
}
// 返回结果和错误
func sqrt(x float64) (float64, error) {
if x < 0 {
return 0, fmt.Errorf("不能计算负数的平方根: %f", x)
}
// 简单的牛顿法计算平方根
guess := x / 2
for i := 0; i < 10; i++ {
guess = (guess + x/guess) / 2
}
return guess, nil
}
func main() {
fmt.Println("=== 基本错误处理 ===")
// 正常情况
if result, err := divide(10, 2); err != nil {
fmt.Printf("错误: %v\n", err)
} else {
fmt.Printf("10 / 2 = %.2f\n", result)
}
// 错误情况
if result, err := divide(10, 0); err != nil {
fmt.Printf("错误: %v\n", err)
} else {
fmt.Printf("10 / 0 = %.2f\n", result)
}
// 验证年龄
fmt.Println("\n=== 年龄验证 ===")
ages := []int{25, -5, 200, 30}
for _, age := range ages {
if err := validateAge(age); err != nil {
fmt.Printf("年龄 %d 无效: %v\n", age, err)
} else {
fmt.Printf("年龄 %d 有效\n", age)
}
}
// 平方根计算
fmt.Println("\n=== 平方根计算 ===")
numbers := []float64{16, 25, -4, 0}
for _, num := range numbers {
if result, err := sqrt(num); err != nil {
fmt.Printf("错误: %v\n", err)
} else {
fmt.Printf("√%.1f = %.6f\n", num, result)
}
}
}自定义错误类型
go
package main
import (
"fmt"
"time"
)
// 自定义错误类型
type ValidationError struct {
Field string
Value interface{}
Message string
}
func (ve ValidationError) Error() string {
return fmt.Sprintf("验证失败 [%s]: %s (值: %v)", ve.Field, ve.Message, ve.Value)
}
// 网络错误类型
type NetworkError struct {
Operation string
URL string
Timestamp time.Time
Err error
}
func (ne NetworkError) Error() string {
return fmt.Sprintf("网络错误 [%s] %s at %v: %v",
ne.Operation, ne.URL, ne.Timestamp.Format("15:04:05"), ne.Err)
}
// 实现 Unwrap 方法,支持错误链
func (ne NetworkError) Unwrap() error {
return ne.Err
}
// 用户注册验证
func validateUser(name, email string, age int) error {
if name == "" {
return ValidationError{
Field: "name",
Value: name,
Message: "用户名不能为空",
}
}
if len(name) < 2 {
return ValidationError{
Field: "name",
Value: name,
Message: "用户名至少需要2个字符",
}
}
if email == "" {
return ValidationError{
Field: "email",
Value: email,
Message: "邮箱不能为空",
}
}
if age < 18 {
return ValidationError{
Field: "age",
Value: age,
Message: "年龄必须满18岁",
}
}
return nil
}
// 模拟网络请求
func fetchUserData(userID string) (map[string]interface{}, error) {
if userID == "" {
return nil, ValidationError{
Field: "userID",
Value: userID,
Message: "用户ID不能为空",
}
}
if userID == "invalid" {
return nil, NetworkError{
Operation: "GET",
URL: fmt.Sprintf("/api/users/%s", userID),
Timestamp: time.Now(),
Err: fmt.Errorf("用户不存在"),
}
}
if userID == "timeout" {
return nil, NetworkError{
Operation: "GET",
URL: fmt.Sprintf("/api/users/%s", userID),
Timestamp: time.Now(),
Err: fmt.Errorf("请求超时"),
}
}
// 模拟成功响应
return map[string]interface{}{
"id": userID,
"name": "张三",
"age": 25,
}, nil
}
func main() {
fmt.Println("=== 自定义错误类型 ===")
// 用户验证测试
fmt.Println("用户验证测试:")
testCases := []struct {
name string
email string
age int
}{
{"张三", "zhangsan@example.com", 25},
{"", "empty@example.com", 20},
{"A", "short@example.com", 30},
{"李四", "", 22},
{"王五", "wangwu@example.com", 15},
}
for _, tc := range testCases {
if err := validateUser(tc.name, tc.email, tc.age); err != nil {
fmt.Printf(" %v\n", err)
// 类型断言检查具体错误类型
if ve, ok := err.(ValidationError); ok {
fmt.Printf(" 错误字段: %s\n", ve.Field)
}
} else {
fmt.Printf(" 用户 %s 验证通过\n", tc.name)
}
}
// 网络请求测试
fmt.Println("\n网络请求测试:")
userIDs := []string{"user123", "", "invalid", "timeout"}
for _, id := range userIDs {
if data, err := fetchUserData(id); err != nil {
fmt.Printf(" 请求失败: %v\n", err)
// 检查错误类型
switch e := err.(type) {
case ValidationError:
fmt.Printf(" 验证错误: 字段 %s\n", e.Field)
case NetworkError:
fmt.Printf(" 网络错误: %s %s\n", e.Operation, e.URL)
}
} else {
fmt.Printf(" 用户数据: %v\n", data)
}
}
}🔄 错误包装和链式处理
错误包装(Go 1.13+)
go
package main
import (
"errors"
"fmt"
)
// 数据层错误
func dataLayerError() error {
return errors.New("数据库连接失败")
}
// 业务层错误
func businessLayerError() error {
err := dataLayerError()
if err != nil {
return fmt.Errorf("业务处理失败: %w", err)
}
return nil
}
// 控制层错误
func controllerLayerError() error {
err := businessLayerError()
if err != nil {
return fmt.Errorf("请求处理失败: %w", err)
}
return nil
}
// 自定义错误类型用于包装
type ServiceError struct {
Service string
Code int
Err error
}
func (se ServiceError) Error() string {
return fmt.Sprintf("服务错误 [%s:%d]: %v", se.Service, se.Code, se.Err)
}
func (se ServiceError) Unwrap() error {
return se.Err
}
// 模拟服务调用
func callUserService(userID string) error {
if userID == "" {
baseErr := errors.New("用户ID为空")
return ServiceError{
Service: "UserService",
Code: 400,
Err: baseErr,
}
}
if userID == "not_found" {
baseErr := errors.New("用户不存在")
return ServiceError{
Service: "UserService",
Code: 404,
Err: baseErr,
}
}
return nil
}
func callOrderService(orderID string) error {
if err := callUserService("not_found"); err != nil {
return fmt.Errorf("订单服务调用用户服务失败: %w", err)
}
return nil
}
func main() {
fmt.Println("=== 错误包装和链式处理 ===")
// 基本错误链
fmt.Println("错误链演示:")
if err := controllerLayerError(); err != nil {
fmt.Printf("最终错误: %v\n", err)
// 解包错误链
fmt.Println("错误链追踪:")
currentErr := err
level := 1
for currentErr != nil {
fmt.Printf(" 第%d层: %v\n", level, currentErr)
// 使用 errors.Unwrap 解包
currentErr = errors.Unwrap(currentErr)
level++
}
}
// 使用 errors.Is 检查特定错误
fmt.Println("\n使用 errors.Is 检查错误:")
targetErr := errors.New("数据库连接失败")
if err := controllerLayerError(); err != nil {
if errors.Is(err, targetErr) {
fmt.Println("错误链中包含数据库连接错误")
} else {
fmt.Println("错误链中不包含数据库连接错误")
}
}
// 使用 errors.As 获取特定错误类型
fmt.Println("\n使用 errors.As 获取错误类型:")
if err := callOrderService("order123"); err != nil {
fmt.Printf("订单服务错误: %v\n", err)
var serviceErr ServiceError
if errors.As(err, &serviceErr) {
fmt.Printf("找到服务错误: 服务=%s, 代码=%d\n",
serviceErr.Service, serviceErr.Code)
}
}
// 多层错误包装
fmt.Println("\n多层错误包装:")
userErr := callUserService("")
if userErr != nil {
wrappedErr := fmt.Errorf("API调用失败: %w", userErr)
finalErr := fmt.Errorf("处理用户请求失败: %w", wrappedErr)
fmt.Printf("最终错误: %v\n", finalErr)
// 检查原始错误类型
var original ServiceError
if errors.As(finalErr, &original) {
fmt.Printf("原始服务错误代码: %d\n", original.Code)
}
}
}🎯 错误处理模式
错误处理策略
go
package main
import (
"errors"
"fmt"
"log"
"time"
)
// 重试策略
type RetryConfig struct {
MaxAttempts int
Delay time.Duration
BackoffRate float64
}
func withRetry(fn func() error, config RetryConfig) error {
var lastErr error
delay := config.Delay
for attempt := 1; attempt <= config.MaxAttempts; attempt++ {
err := fn()
if err == nil {
return nil
}
lastErr = err
if attempt == config.MaxAttempts {
break
}
fmt.Printf("第%d次尝试失败: %v,%v后重试\n", attempt, err, delay)
time.Sleep(delay)
// 指数退避
delay = time.Duration(float64(delay) * config.BackoffRate)
}
return fmt.Errorf("重试%d次后仍失败,最后错误: %w", config.MaxAttempts, lastErr)
}
// 模拟不稳定的操作
var attemptCount = 0
func unstableOperation() error {
attemptCount++
if attemptCount < 3 {
return errors.New("操作失败")
}
attemptCount = 0 // 重置计数器
return nil
}
// 错误聚合
type ErrorCollector struct {
errors []error
}
func (ec *ErrorCollector) Add(err error) {
if err != nil {
ec.errors = append(ec.errors, err)
}
}
func (ec *ErrorCollector) HasErrors() bool {
return len(ec.errors) > 0
}
func (ec *ErrorCollector) Error() string {
if len(ec.errors) == 0 {
return ""
}
result := fmt.Sprintf("发现%d个错误:\n", len(ec.errors))
for i, err := range ec.errors {
result += fmt.Sprintf(" %d. %v\n", i+1, err)
}
return result
}
func (ec *ErrorCollector) Errors() []error {
return ec.errors
}
// 批量验证
func validateBatch(items []string) error {
collector := &ErrorCollector{}
for i, item := range items {
if item == "" {
collector.Add(fmt.Errorf("索引%d: 项目不能为空", i))
} else if len(item) < 3 {
collector.Add(fmt.Errorf("索引%d: 项目'%s'长度不能少于3个字符", i, item))
}
}
if collector.HasErrors() {
return collector
}
return nil
}
// 恐慌恢复
func safeDivide(a, b float64) (result float64, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("计算发生恐慌: %v", r)
}
}()
if b == 0 {
panic("除数为零")
}
return a / b, nil
}
// 资源清理模式
type Resource struct {
name string
opened bool
}
func NewResource(name string) *Resource {
fmt.Printf("打开资源: %s\n", name)
return &Resource{name: name, opened: true}
}
func (r *Resource) Close() error {
if !r.opened {
return errors.New("资源已关闭")
}
fmt.Printf("关闭资源: %s\n", r.name)
r.opened = false
return nil
}
func (r *Resource) Use() error {
if !r.opened {
return errors.New("资源未打开")
}
if r.name == "error_resource" {
return errors.New("资源使用失败")
}
fmt.Printf("使用资源: %s\n", r.name)
return nil
}
func useResourceSafely(name string) (err error) {
resource := NewResource(name)
// 确保资源被正确关闭
defer func() {
if closeErr := resource.Close(); closeErr != nil {
if err == nil {
err = closeErr
} else {
err = fmt.Errorf("原错误: %v; 关闭错误: %v", err, closeErr)
}
}
}()
return resource.Use()
}
func main() {
fmt.Println("=== 错误处理模式 ===")
// 重试模式
fmt.Println("1. 重试模式:")
config := RetryConfig{
MaxAttempts: 5,
Delay: 100 * time.Millisecond,
BackoffRate: 1.5,
}
err := withRetry(unstableOperation, config)
if err != nil {
fmt.Printf("最终失败: %v\n", err)
} else {
fmt.Println("操作成功!")
}
// 错误聚合模式
fmt.Println("\n2. 错误聚合模式:")
items := []string{"有效项目", "", "ab", "另一个有效项目", "x"}
if err := validateBatch(items); err != nil {
fmt.Printf("批量验证失败:\n%v", err)
} else {
fmt.Println("批量验证通过")
}
// 恐慌恢复模式
fmt.Println("\n3. 恐慌恢复模式:")
if result, err := safeDivide(10, 2); err != nil {
fmt.Printf("计算失败: %v\n", err)
} else {
fmt.Printf("10 / 2 = %.2f\n", result)
}
if result, err := safeDivide(10, 0); err != nil {
fmt.Printf("计算失败: %v\n", err)
} else {
fmt.Printf("10 / 0 = %.2f\n", result)
}
// 资源清理模式
fmt.Println("\n4. 资源清理模式:")
fmt.Println("正常使用资源:")
if err := useResourceSafely("normal_resource"); err != nil {
fmt.Printf("使用失败: %v\n", err)
}
fmt.Println("\n错误使用资源:")
if err := useResourceSafely("error_resource"); err != nil {
fmt.Printf("使用失败: %v\n", err)
}
}💼 实际应用示例
Web API 错误处理
go
package main
import (
"encoding/json"
"fmt"
"net/http"
)
// API 错误响应结构
type APIError struct {
Code int `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
}
func (ae APIError) Error() string {
return fmt.Sprintf("API错误 %d: %s", ae.Code, ae.Message)
}
// HTTP 状态码常量
const (
StatusBadRequest = 400
StatusUnauthorized = 401
StatusNotFound = 404
StatusInternalServerError = 500
)
// 创建不同类型的 API 错误
func NewBadRequestError(message string) APIError {
return APIError{
Code: StatusBadRequest,
Message: "请求参数错误",
Details: message,
}
}
func NewNotFoundError(resource string) APIError {
return APIError{
Code: StatusNotFound,
Message: "资源未找到",
Details: fmt.Sprintf("资源 '%s' 不存在", resource),
}
}
func NewInternalError(details string) APIError {
return APIError{
Code: StatusInternalServerError,
Message: "服务器内部错误",
Details: details,
}
}
// 模拟用户服务
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
var users = map[int]User{
1: {ID: 1, Name: "张三", Age: 25},
2: {ID: 2, Name: "李四", Age: 30},
3: {ID: 3, Name: "王五", Age: 35},
}
// 获取用户
func getUser(id int) (User, error) {
if id <= 0 {
return User{}, NewBadRequestError("用户ID必须大于0")
}
user, exists := users[id]
if !exists {
return User{}, NewNotFoundError(fmt.Sprintf("用户ID=%d", id))
}
return user, nil
}
// 创建用户
func createUser(name string, age int) (User, error) {
if name == "" {
return User{}, NewBadRequestError("用户名不能为空")
}
if age < 0 || age > 150 {
return User{}, NewBadRequestError("年龄必须在0-150之间")
}
// 模拟数据库错误
if name == "error" {
return User{}, NewInternalError("数据库保存失败")
}
// 生成新用户ID
newID := len(users) + 1
user := User{
ID: newID,
Name: name,
Age: age,
}
users[newID] = user
return user, nil
}
// HTTP 错误响应
func writeErrorResponse(w http.ResponseWriter, err error) {
var apiErr APIError
var statusCode int
// 检查是否是 API 错误
if ae, ok := err.(APIError); ok {
apiErr = ae
statusCode = ae.Code
} else {
// 未知错误,返回内部服务器错误
apiErr = NewInternalError(err.Error())
statusCode = StatusInternalServerError
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(apiErr)
}
// HTTP 成功响应
func writeSuccessResponse(w http.ResponseWriter, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": true,
"data": data,
})
}
// 模拟 HTTP 处理器
func handleGetUser(w http.ResponseWriter, r *http.Request) {
// 这里简化处理,实际应该从URL路径或参数中获取ID
userID := 1 // 模拟获取到的用户ID
user, err := getUser(userID)
if err != nil {
writeErrorResponse(w, err)
return
}
writeSuccessResponse(w, user)
}
func main() {
fmt.Println("=== Web API 错误处理演示 ===")
// 模拟不同的API调用场景
fmt.Println("1. 正常获取用户:")
if user, err := getUser(1); err != nil {
fmt.Printf("错误: %v\n", err)
} else {
fmt.Printf("用户: %+v\n", user)
}
fmt.Println("\n2. 获取不存在的用户:")
if user, err := getUser(999); err != nil {
fmt.Printf("错误: %v\n", err)
// 检查具体错误类型
if apiErr, ok := err.(APIError); ok {
fmt.Printf("错误码: %d\n", apiErr.Code)
fmt.Printf("错误详情: %s\n", apiErr.Details)
}
} else {
fmt.Printf("用户: %+v\n", user)
}
fmt.Println("\n3. 无效的用户ID:")
if user, err := getUser(-1); err != nil {
fmt.Printf("错误: %v\n", err)
} else {
fmt.Printf("用户: %+v\n", user)
}
fmt.Println("\n4. 创建用户:")
if user, err := createUser("赵六", 28); err != nil {
fmt.Printf("创建失败: %v\n", err)
} else {
fmt.Printf("创建成功: %+v\n", user)
}
fmt.Println("\n5. 创建用户时参数错误:")
if user, err := createUser("", 25); err != nil {
fmt.Printf("创建失败: %v\n", err)
} else {
fmt.Printf("创建成功: %+v\n", user)
}
fmt.Println("\n6. 模拟服务器内部错误:")
if user, err := createUser("error", 25); err != nil {
fmt.Printf("创建失败: %v\n", err)
} else {
fmt.Printf("创建成功: %+v\n", user)
}
// 演示 JSON 错误响应格式
fmt.Println("\n7. JSON 错误响应示例:")
err := NewNotFoundError("用户ID=999")
jsonData, _ := json.MarshalIndent(err, "", " ")
fmt.Printf("JSON响应:\n%s\n", jsonData)
}🎓 小结
本章我们深入学习了 Go 语言的错误处理:
- ✅ 错误基础:error 接口和基本错误处理模式
- ✅ 自定义错误:创建自定义错误类型和结构
- ✅ 错误包装:错误链式处理和包装机制
- ✅ 处理模式:重试、聚合、恐慌恢复等策略
- ✅ 实际应用:Web API 错误处理的完整示例
- ✅ 最佳实践:错误处理的设计原则和技巧
Go 语言的错误处理机制虽然看起来冗长,但它让错误处理变得明确和可预测,是编写健壮程序的重要基础。
接下来,我们将学习 Go 并发模型和并发安全,这是 Go 语言最重要的特性之一。
错误处理建议
- 总是检查和处理错误,不要忽略
- 使用有意义的错误消息,便于调试
- 合理使用错误包装,保留错误上下文
- 对于可恢复的错误,考虑重试机制