# 第8章:错误处理
学习目标
通过本章学习,你将掌握: - Go语言的错误处理哲学 - error接口和自定义错误类型 - 错误处理的最佳实践 - 错误包装和链式错误 - panic和recover机制 - 错误处理模式和技巧
Go语言的错误处理哲学
错误处理基础
错误作为值
package main
import (
"errors"
"fmt"
"strconv"
)
func main() {
fmt.Println("=== Go语言错误处理基础 ===")
fmt.Println("\n1. Go错误处理哲学:")
fmt.Println(" - 错误是值,不是异常")
fmt.Println(" - 显式错误检查")
fmt.Println(" - 错误应该被处理,而不是忽略")
fmt.Println(" - 简单、明确、可预测")
fmt.Println("\n2. error接口:")
fmt.Println(" type error interface {")
fmt.Println(" Error() string")
fmt.Println(" }")
// 演示基本错误处理
demonstrateBasicErrorHandling()
// 演示错误创建
demonstrateErrorCreation()
// 演示错误检查模式
demonstrateErrorCheckingPatterns()
}
func demonstrateBasicErrorHandling() {
fmt.Println("\n=== 基本错误处理 ===")
// 使用标准库函数的错误处理
str := "123"
if num, err := strconv.Atoi(str); err != nil {
fmt.Printf("转换失败: %v\n", err)
} else {
fmt.Printf("转换成功: %d\n", num)
}
// 错误的字符串转换
str = "abc"
if num, err := strconv.Atoi(str); err != nil {
fmt.Printf("转换失败: %v\n", err)
fmt.Printf("错误类型: %T\n", err)
} else {
fmt.Printf("转换成功: %d\n", num)
}
// 自定义函数的错误处理
result, err := divide(10, 2)
if err != nil {
fmt.Printf("除法错误: %v\n", err)
} else {
fmt.Printf("10 / 2 = %.2f\n", result)
}
result, err = divide(10, 0)
if err != nil {
fmt.Printf("除法错误: %v\n", err)
} else {
fmt.Printf("10 / 0 = %.2f\n", result)
}
}
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为零")
}
return a / b, nil
}
func demonstrateErrorCreation() {
fmt.Println("\n=== 错误创建方式 ===")
// 1. 使用errors.New
err1 := errors.New("这是一个简单错误")
fmt.Printf("errors.New: %v\n", err1)
// 2. 使用fmt.Errorf
name := "Alice"
age := -5
err2 := fmt.Errorf("用户 %s 的年龄 %d 无效", name, age)
fmt.Printf("fmt.Errorf: %v\n", err2)
// 3. 自定义错误类型
err3 := &ValidationError{
Field: "email",
Value: "invalid-email",
Message: "邮箱格式不正确",
}
fmt.Printf("自定义错误: %v\n", err3)
// 4. 错误包装
originalErr := errors.New("原始错误")
wrappedErr := fmt.Errorf("包装错误: %w", originalErr)
fmt.Printf("包装错误: %v\n", wrappedErr)
}
// 自定义错误类型
type ValidationError struct {
Field string
Value string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("验证失败 [%s=%s]: %s", e.Field, e.Value, e.Message)
}
func demonstrateErrorCheckingPatterns() {
fmt.Println("\n=== 错误检查模式 ===")
// 模式1:立即检查
if result, err := riskyOperation("success"); err != nil {
fmt.Printf("操作失败: %v\n", err)
} else {
fmt.Printf("操作成功: %s\n", result)
}
// 模式2:延迟检查(不推荐)
result, err := riskyOperation("fail")
if err != nil {
fmt.Printf("操作失败: %v\n", err)
return
}
fmt.Printf("操作成功: %s\n", result)
}
func riskyOperation(mode string) (string, error) {
if mode == "fail" {
return "", errors.New("操作失败")
}
return "操作结果", nil
}
go run error_basics.go
错误处理最佳实践
错误处理原则
package main
import (
"errors"
"fmt"
"io"
"os"
"time"
)
func main() {
fmt.Println("=== 错误处理最佳实践 ===")
// 1. 总是检查错误
demonstrateAlwaysCheckErrors()
// 2. 提供有意义的错误信息
demonstrateMeaningfulErrors()
// 3. 错误处理策略
demonstrateErrorStrategies()
// 4. 错误传播
demonstrateErrorPropagation()
}
func demonstrateAlwaysCheckErrors() {
fmt.Println("\n=== 1. 总是检查错误 ===")
// 好的做法:检查每个可能的错误
filename := "test.txt"
// 创建文件
file, err := os.Create(filename)
if err != nil {
fmt.Printf("创建文件失败: %v\n", err)
return
}
defer func() {
if closeErr := file.Close(); closeErr != nil {
fmt.Printf("关闭文件失败: %v\n", closeErr)
}
}()
// 写入数据
data := "Hello, Error Handling!"
if _, err := file.WriteString(data); err != nil {
fmt.Printf("写入文件失败: %v\n", err)
return
}
fmt.Printf("成功写入文件: %s\n", filename)
// 清理
if err := os.Remove(filename); err != nil {
fmt.Printf("删除文件失败: %v\n", err)
}
}
func demonstrateMeaningfulErrors() {
fmt.Println("\n=== 2. 提供有意义的错误信息 ===")
// 不好的错误信息
_, err := badFunction("")
if err != nil {
fmt.Printf("不好的错误: %v\n", err)
}
// 好的错误信息
_, err = goodFunction("")
if err != nil {
fmt.Printf("好的错误: %v\n", err)
}
// 带上下文的错误
err = processUser(User{ID: 0, Name: "", Email: "invalid"})
if err != nil {
fmt.Printf("处理用户错误: %v\n", err)
}
}
func badFunction(input string) (string, error) {
if input == "" {
return "", errors.New("错误")
}
return input, nil
}
func goodFunction(input string) (string, error) {
if input == "" {
return "", errors.New("输入参数不能为空字符串")
}
return input, nil
}
type User struct {
ID int
Name string
Email string
}
func processUser(user User) error {
if user.ID <= 0 {
return fmt.Errorf("用户ID无效: %d (必须大于0)", user.ID)
}
if user.Name == "" {
return fmt.Errorf("用户名不能为空 (用户ID: %d)", user.ID)
}
if !isValidEmail(user.Email) {
return fmt.Errorf("邮箱格式无效: %s (用户: %s, ID: %d)",
user.Email, user.Name, user.ID)
}
return nil
}
func isValidEmail(email string) bool {
return len(email) > 0 && email != "invalid"
}
func demonstrateErrorStrategies() {
fmt.Println("\n=== 3. 错误处理策略 ===")
// 策略1:立即返回错误
fmt.Println("\n策略1:立即返回错误")
if err := strategyFailFast(); err != nil {
fmt.Printf("快速失败: %v\n", err)
}
// 策略2:重试机制
fmt.Println("\n策略2:重试机制")
if result, err := strategyRetry(); err != nil {
fmt.Printf("重试后仍失败: %v\n", err)
} else {
fmt.Printf("重试成功: %s\n", result)
}
// 策略3:降级处理
fmt.Println("\n策略3:降级处理")
result := strategyFallback()
fmt.Printf("降级结果: %s\n", result)
// 策略4:记录并继续
fmt.Println("\n策略4:记录并继续")
strategyLogAndContinue()
}
func strategyFailFast() error {
// 模拟一个会失败的操作
if err := simulateFailure(); err != nil {
return fmt.Errorf("操作失败,立即返回: %w", err)
}
return nil
}
func strategyRetry() (string, error) {
maxRetries := 3
for i := 0; i < maxRetries; i++ {
if result, err := simulateUnstableOperation(i); err != nil {
fmt.Printf("第 %d 次尝试失败: %v\n", i+1, err)
if i == maxRetries-1 {
return "", fmt.Errorf("重试 %d 次后仍然失败: %w", maxRetries, err)
}
time.Sleep(100 * time.Millisecond) // 等待后重试
} else {
return result, nil
}
}
return "", errors.New("不应该到达这里")
}
func strategyFallback() string {
// 尝试主要操作
if result, err := primaryOperation(); err != nil {
fmt.Printf("主要操作失败: %v,使用备用方案\n", err)
return fallbackOperation()
} else {
return result
}
}
func strategyLogAndContinue() {
operations := []string{"op1", "op2", "op3"}
for _, op := range operations {
if err := simulateOperation(op); err != nil {
// 记录错误但继续处理其他操作
fmt.Printf("操作 %s 失败: %v (继续处理其他操作)\n", op, err)
} else {
fmt.Printf("操作 %s 成功\n", op)
}
}
}
func simulateFailure() error {
return errors.New("模拟失败")
}
func simulateUnstableOperation(attempt int) (string, error) {
if attempt < 2 {
return "", fmt.Errorf("不稳定操作失败 (尝试 %d)", attempt+1)
}
return "成功结果", nil
}
func primaryOperation() (string, error) {
return "", errors.New("主要操作失败")
}
func fallbackOperation() string {
return "备用操作结果"
}
func simulateOperation(name string) error {
if name == "op2" {
return fmt.Errorf("操作 %s 失败", name)
}
return nil
}
func demonstrateErrorPropagation() {
fmt.Println("\n=== 4. 错误传播 ===")
// 演示错误在调用栈中的传播
if err := levelOne(); err != nil {
fmt.Printf("顶层捕获错误: %v\n", err)
}
}
func levelOne() error {
if err := levelTwo(); err != nil {
return fmt.Errorf("level1 处理失败: %w", err)
}
return nil
}
func levelTwo() error {
if err := levelThree(); err != nil {
return fmt.Errorf("level2 处理失败: %w", err)
}
return nil
}
func levelThree() error {
return errors.New("level3 底层错误")
}
go run error_best_practices.go
自定义错误类型
创建自定义错误
实现error接口
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("=== 自定义错误类型 ===")
// 演示不同类型的自定义错误
demonstrateCustomErrors()
// 演示错误类型断言
demonstrateErrorTypeAssertion()
// 演示错误分类
demonstrateErrorClassification()
}
// 1. 简单自定义错误
type SimpleError struct {
Message string
Code int
}
func (e SimpleError) Error() string {
return fmt.Sprintf("错误 [%d]: %s", e.Code, e.Message)
}
// 2. 带时间戳的错误
type TimestampedError struct {
Message string
Timestamp time.Time
Component string
}
func (e TimestampedError) Error() string {
return fmt.Sprintf("[%s] %s: %s",
e.Timestamp.Format("2006-01-02 15:04:05"),
e.Component, e.Message)
}
// 3. 网络错误
type NetworkError struct {
Op string // 操作类型
Network string // 网络类型
Address string // 地址
Err error // 底层错误
Timeout bool // 是否超时
Temporary bool // 是否临时错误
}
func (e *NetworkError) Error() string {
return fmt.Sprintf("%s %s %s: %v", e.Op, e.Network, e.Address, e.Err)
}
func (e *NetworkError) IsTimeout() bool {
return e.Timeout
}
func (e *NetworkError) IsTemporary() bool {
return e.Temporary
}
// 4. 验证错误(包含多个字段错误)
type ValidationError struct {
Errors map[string]string
}
func (e ValidationError) Error() string {
if len(e.Errors) == 0 {
return "验证失败"
}
msg := "验证失败:\n"
for field, err := range e.Errors {
msg += fmt.Sprintf(" %s: %s\n", field, err)
}
return msg
}
func (e ValidationError) HasErrors() bool {
return len(e.Errors) > 0
}
func (e ValidationError) GetError(field string) (string, bool) {
err, exists := e.Errors[field]
return err, exists
}
// 5. 业务错误
type BusinessError struct {
Code string
Message string
Details map[string]interface{}
}
func (e BusinessError) Error() string {
return fmt.Sprintf("业务错误 [%s]: %s", e.Code, e.Message)
}
func (e BusinessError) GetCode() string {
return e.Code
}
func (e BusinessError) GetDetails() map[string]interface{} {
return e.Details
}
func demonstrateCustomErrors() {
fmt.Println("\n=== 自定义错误演示 ===")
// 简单错误
err1 := SimpleError{Message: "文件不存在", Code: 404}
fmt.Printf("简单错误: %v\n", err1)
// 带时间戳的错误
err2 := TimestampedError{
Message: "数据库连接失败",
Timestamp: time.Now(),
Component: "Database",
}
fmt.Printf("时间戳错误: %v\n", err2)
// 网络错误
err3 := &NetworkError{
Op: "dial",
Network: "tcp",
Address: "localhost:8080",
Err: fmt.Errorf("connection refused"),
Timeout: false,
Temporary: true,
}
fmt.Printf("网络错误: %v\n", err3)
fmt.Printf("是否超时: %t\n", err3.IsTimeout())
fmt.Printf("是否临时: %t\n", err3.IsTemporary())
// 验证错误
err4 := ValidationError{
Errors: map[string]string{
"name": "名称不能为空",
"email": "邮箱格式不正确",
"age": "年龄必须大于0",
},
}
fmt.Printf("验证错误: %v\n", err4)
// 业务错误
err5 := BusinessError{
Code: "INSUFFICIENT_BALANCE",
Message: "账户余额不足",
Details: map[string]interface{}{
"balance": 100.0,
"required": 150.0,
"account": "12345",
},
}
fmt.Printf("业务错误: %v\n", err5)
}
func demonstrateErrorTypeAssertion() {
fmt.Println("\n=== 错误类型断言 ===")
errors := []error{
SimpleError{Message: "简单错误", Code: 500},
&NetworkError{Op: "read", Network: "tcp", Address: "example.com:80", Timeout: true},
ValidationError{Errors: map[string]string{"field": "invalid"}},
BusinessError{Code: "AUTH_FAILED", Message: "认证失败"},
}
for i, err := range errors {
fmt.Printf("\n错误 %d: %v\n", i+1, err)
// 类型断言
switch e := err.(type) {
case SimpleError:
fmt.Printf(" 类型: 简单错误, 代码: %d\n", e.Code)
case *NetworkError:
fmt.Printf(" 类型: 网络错误, 操作: %s\n", e.Op)
if e.IsTimeout() {
fmt.Printf(" 这是一个超时错误\n")
}
case ValidationError:
fmt.Printf(" 类型: 验证错误, 字段数: %d\n", len(e.Errors))
case BusinessError:
fmt.Printf(" 类型: 业务错误, 代码: %s\n", e.GetCode())
default:
fmt.Printf(" 类型: 未知错误类型\n")
}
}
}
func demonstrateErrorClassification() {
fmt.Println("\n=== 错误分类处理 ===")
// 模拟不同类型的操作错误
operations := []func() error{
func() error { return simulateFileOperation() },
func() error { return simulateNetworkOperation() },
func() error { return simulateValidationOperation() },
func() error { return simulateBusinessOperation() },
}
for i, op := range operations {
fmt.Printf("\n执行操作 %d:\n", i+1)
if err := op(); err != nil {
handleError(err)
} else {
fmt.Printf(" 操作成功\n")
}
}
}
func simulateFileOperation() error {
return SimpleError{Message: "文件权限不足", Code: 403}
}
func simulateNetworkOperation() error {
return &NetworkError{
Op: "connect",
Network: "tcp",
Address: "api.example.com:443",
Err: fmt.Errorf("timeout"),
Timeout: true,
Temporary: true,
}
}
func simulateValidationOperation() error {
return ValidationError{
Errors: map[string]string{
"username": "用户名已存在",
"password": "密码强度不够",
},
}
}
func simulateBusinessOperation() error {
return BusinessError{
Code: "QUOTA_EXCEEDED",
Message: "已超出配额限制",
Details: map[string]interface{}{
"current": 1000,
"limit": 500,
"period": "daily",
},
}
}
func handleError(err error) {
fmt.Printf(" 错误: %v\n", err)
switch e := err.(type) {
case SimpleError:
if e.Code >= 500 {
fmt.Printf(" 处理策略: 服务器错误,记录日志并通知管理员\n")
} else if e.Code >= 400 {
fmt.Printf(" 处理策略: 客户端错误,返回错误信息\n")
}
case *NetworkError:
if e.IsTimeout() {
fmt.Printf(" 处理策略: 网络超时,建议重试\n")
} else if e.IsTemporary() {
fmt.Printf(" 处理策略: 临时网络错误,稍后重试\n")
} else {
fmt.Printf(" 处理策略: 永久网络错误,检查网络配置\n")
}
case ValidationError:
fmt.Printf(" 处理策略: 验证错误,返回详细的字段错误信息\n")
for field, msg := range e.Errors {
fmt.Printf(" %s: %s\n", field, msg)
}
case BusinessError:
fmt.Printf(" 处理策略: 业务错误,根据错误代码执行相应逻辑\n")
fmt.Printf(" 错误代码: %s\n", e.GetCode())
if details := e.GetDetails(); len(details) > 0 {
fmt.Printf(" 详细信息: %+v\n", details)
}
default:
fmt.Printf(" 处理策略: 未知错误类型,使用默认处理\n")
}
}
go run custom_errors.go
错误包装和链式错误
错误包装
使用fmt.Errorf和%w动词
package main
import (
"errors"
"fmt"
"os"
)
func main() {
fmt.Println("=== 错误包装和链式错误 ===")
// 演示错误包装
demonstrateErrorWrapping()
// 演示错误解包
demonstrateErrorUnwrapping()
// 演示错误链
demonstrateErrorChain()
// 演示errors.Is和errors.As
demonstrateErrorsIsAs()
}
func demonstrateErrorWrapping() {
fmt.Println("\n=== 错误包装 ===")
// 原始错误
originalErr := errors.New("数据库连接失败")
fmt.Printf("原始错误: %v\n", originalErr)
// 包装错误(添加上下文)
wrappedErr := fmt.Errorf("用户服务初始化失败: %w", originalErr)
fmt.Printf("包装错误: %v\n", wrappedErr)
// 多层包装
doubleWrappedErr := fmt.Errorf("应用启动失败: %w", wrappedErr)
fmt.Printf("双重包装: %v\n", doubleWrappedErr)
// 检查错误链
fmt.Printf("\n错误链检查:\n")
if errors.Is(doubleWrappedErr, originalErr) {
fmt.Printf(" 双重包装错误包含原始错误\n")
}
// 获取根本原因
rootCause := errors.Unwrap(errors.Unwrap(doubleWrappedErr))
fmt.Printf(" 根本原因: %v\n", rootCause)
}
func demonstrateErrorUnwrapping() {
fmt.Println("\n=== 错误解包 ===")
// 创建错误链
err := createErrorChain()
fmt.Printf("完整错误: %v\n", err)
// 逐层解包
fmt.Printf("\n逐层解包:\n")
current := err
level := 1
for current != nil {
fmt.Printf(" 层级 %d: %v\n", level, current)
current = errors.Unwrap(current)
level++
}
}
func createErrorChain() error {
// 模拟深层调用栈中的错误
if err := databaseOperation(); err != nil {
return fmt.Errorf("业务逻辑处理失败: %w", err)
}
return nil
}
func databaseOperation() error {
if err := connectionPool(); err != nil {
return fmt.Errorf("数据库操作失败: %w", err)
}
return nil
}
func connectionPool() error {
if err := networkCall(); err != nil {
return fmt.Errorf("连接池获取连接失败: %w", err)
}
return nil
}
func networkCall() error {
return &NetworkError{
Op: "dial",
Network: "tcp",
Address: "db.example.com:5432",
Err: errors.New("connection refused"),
Timeout: false,
}
}
// 自定义网络错误(支持Unwrap)
type NetworkError struct {
Op string
Network string
Address string
Err error
Timeout bool
}
func (e *NetworkError) Error() string {
return fmt.Sprintf("%s %s %s: %v", e.Op, e.Network, e.Address, e.Err)
}
// 实现Unwrap方法以支持错误链
func (e *NetworkError) Unwrap() error {
return e.Err
}
func demonstrateErrorChain() {
fmt.Println("\n=== 错误链处理 ===")
// 创建复杂的错误链
err := processRequest("invalid-request")
if err != nil {
fmt.Printf("请求处理失败: %v\n", err)
// 分析错误链
analyzeErrorChain(err)
}
}
func processRequest(requestID string) error {
if err := validateRequest(requestID); err != nil {
return fmt.Errorf("请求处理失败 [ID: %s]: %w", requestID, err)
}
return nil
}
func validateRequest(requestID string) error {
if err := checkPermissions(requestID); err != nil {
return fmt.Errorf("请求验证失败: %w", err)
}
return nil
}
func checkPermissions(requestID string) error {
if requestID == "invalid-request" {
return &PermissionError{
User: "anonymous",
Resource: "protected-resource",
Action: "read",
Reason: "用户未认证",
}
}
return nil
}
// 权限错误
type PermissionError struct {
User string
Resource string
Action string
Reason string
}
func (e *PermissionError) Error() string {
return fmt.Sprintf("权限拒绝: 用户 %s 无法对资源 %s 执行 %s 操作 (%s)",
e.User, e.Resource, e.Action, e.Reason)
}
func analyzeErrorChain(err error) {
fmt.Printf("\n错误链分析:\n")
// 检查特定错误类型
var netErr *NetworkError
if errors.As(err, &netErr) {
fmt.Printf(" 包含网络错误: %s %s\n", netErr.Op, netErr.Address)
}
var permErr *PermissionError
if errors.As(err, &permErr) {
fmt.Printf(" 包含权限错误: 用户 %s, 资源 %s\n", permErr.User, permErr.Resource)
}
// 检查特定错误值
if errors.Is(err, os.ErrNotExist) {
fmt.Printf(" 包含文件不存在错误\n")
}
}
func demonstrateErrorsIsAs() {
fmt.Println("\n=== errors.Is 和 errors.As 使用 ===")
// 创建包含不同错误类型的错误链
errors_list := []error{
createNetworkErrorChain(),
createPermissionErrorChain(),
createFileErrorChain(),
}
for i, err := range errors_list {
fmt.Printf("\n错误 %d: %v\n", i+1, err)
// 使用 errors.Is 检查特定错误值
if errors.Is(err, os.ErrNotExist) {
fmt.Printf(" ✓ 包含文件不存在错误\n")
}
if errors.Is(err, os.ErrPermission) {
fmt.Printf(" ✓ 包含权限错误\n")
}
// 使用 errors.As 提取特定错误类型
var netErr *NetworkError
if errors.As(err, &netErr) {
fmt.Printf(" ✓ 包含网络错误: %s\n", netErr.Address)
}
var permErr *PermissionError
if errors.As(err, &permErr) {
fmt.Printf(" ✓ 包含权限错误: %s -> %s\n", permErr.User, permErr.Resource)
}
}
}
func createNetworkErrorChain() error {
netErr := &NetworkError{
Op: "connect",
Network: "tcp",
Address: "api.example.com:443",
Err: errors.New("timeout"),
Timeout: true,
}
return fmt.Errorf("服务调用失败: %w", netErr)
}
func createPermissionErrorChain() error {
permErr := &PermissionError{
User: "guest",
Resource: "admin-panel",
Action: "access",
Reason: "权限不足",
}
return fmt.Errorf("访问控制失败: %w", permErr)
}
func createFileErrorChain() error {
return fmt.Errorf("配置加载失败: %w",
fmt.Errorf("读取配置文件失败: %w", os.ErrNotExist))
}
go run error_wrapping.go
panic和recover机制
理解panic和recover
panic的使用场景
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println("=== panic和recover机制 ===")
// 演示panic的基本使用
demonstratePanicBasics()
// 演示recover的使用
demonstrateRecover()
// 演示panic的传播
demonstratePanicPropagation()
// 演示实际应用场景
demonstratePracticalUsage()
}
func demonstratePanicBasics() {
fmt.Println("\n=== panic基础 ===")
fmt.Println("1. panic的使用场景:")
fmt.Println(" - 程序遇到无法恢复的错误")
fmt.Println(" - 编程错误(如数组越界)")
fmt.Println(" - 不应该发生的情况")
fmt.Println(" - 初始化失败")
fmt.Println("\n2. panic vs error:")
fmt.Println(" - error: 预期的、可处理的错误")
fmt.Println(" - panic: 意外的、程序无法继续的情况")
// 演示会导致panic的情况
fmt.Println("\n3. 常见panic情况:")
// 使用defer和recover来安全演示panic
defer func() {
if r := recover(); r != nil {
fmt.Printf(" 捕获到panic: %v\n", r)
}
}()
// 这些操作会导致panic,但被recover捕获
demonstratePanicCases()
}
func demonstratePanicCases() {
cases := []struct {
name string
fn func()
}{
{"数组越界", func() {
arr := [3]int{1, 2, 3}
_ = arr[5] // 越界访问
}},
{"空指针解引用", func() {
var p *int
_ = *p // 空指针解引用
}},
{"类型断言失败", func() {
var i interface{} = "string"
_ = i.(int) // 错误的类型断言
}},
{"向已关闭的channel发送", func() {
ch := make(chan int)
close(ch)
ch <- 1 // 向已关闭的channel发送
}},
}
for _, c := range cases {
func() {
defer func() {
if r := recover(); r != nil {
fmt.Printf(" %s: %v\n", c.name, r)
}
}()
c.fn()
}()
}
}
func demonstrateRecover() {
fmt.Println("\n=== recover使用 ===")
fmt.Println("\n1. recover的规则:")
fmt.Println(" - 只能在defer函数中调用")
fmt.Println(" - 只能恢复当前goroutine的panic")
fmt.Println(" - 返回panic的值,如果没有panic返回nil")
// 演示正确的recover使用
fmt.Println("\n2. 正确的recover使用:")
result := safeOperation("valid")
fmt.Printf(" 安全操作结果: %s\n", result)
result = safeOperation("panic")
fmt.Printf(" 安全操作结果: %s\n", result)
// 演示错误的recover使用
fmt.Println("\n3. 错误的recover使用:")
incorrectRecoverUsage()
}
func safeOperation(input string) (result string) {
defer func() {
if r := recover(); r != nil {
fmt.Printf(" 捕获panic: %v\n", r)
result = "操作失败,已恢复"
}
}()
if input == "panic" {
panic("模拟panic情况")
}
return "操作成功"
}
func incorrectRecoverUsage() {
// 错误1:不在defer中调用recover
if r := recover(); r != nil {
fmt.Printf(" 这个recover不会工作: %v\n", r)
}
// 错误2:在defer中但不是直接调用
defer func() {
go func() {
if r := recover(); r != nil {
fmt.Printf(" 这个recover也不会工作: %v\n", r)
}
}()
}()
fmt.Println(" 错误的recover使用不会捕获panic")
}
func demonstratePanicPropagation() {
fmt.Println("\n=== panic传播 ===")
defer func() {
if r := recover(); r != nil {
fmt.Printf("main函数捕获panic: %v\n", r)
// 打印调用栈
buf := make([]byte, 1024)
n := runtime.Stack(buf, false)
fmt.Printf("调用栈:\n%s\n", buf[:n])
}
}()
fmt.Println("调用函数链...")
functionA()
}
func functionA() {
fmt.Println("进入函数A")
defer fmt.Println("退出函数A")
functionB()
}
func functionB() {
fmt.Println("进入函数B")
defer fmt.Println("退出函数B")
functionC()
}
func functionC() {
fmt.Println("进入函数C")
defer fmt.Println("退出函数C")
panic("函数C中发生panic")
}
func demonstratePracticalUsage() {
fmt.Println("\n=== 实际应用场景 ===")
// 1. Web服务器中的panic恢复
fmt.Println("\n1. Web服务器panic恢复:")
simulateWebHandler("normal")
simulateWebHandler("panic")
// 2. 工作池中的panic恢复
fmt.Println("\n2. 工作池panic恢复:")
simulateWorkerPool()
// 3. 资源清理
fmt.Println("\n3. 资源清理:")
simulateResourceManagement()
}
func simulateWebHandler(requestType string) {
defer func() {
if r := recover(); r != nil {
fmt.Printf(" HTTP处理器panic恢复: %v\n", r)
fmt.Printf(" 返回500错误给客户端\n")
}
}()
fmt.Printf(" 处理请求: %s\n", requestType)
if requestType == "panic" {
panic("处理请求时发生错误")
}
fmt.Printf(" 请求处理成功\n")
}
func simulateWorkerPool() {
jobs := []string{"job1", "panic-job", "job3"}
for _, job := range jobs {
func(jobName string) {
defer func() {
if r := recover(); r != nil {
fmt.Printf(" 工作任务 %s panic恢复: %v\n", jobName, r)
fmt.Printf(" 工作池继续运行\n")
}
}()
fmt.Printf(" 执行任务: %s\n", jobName)
if jobName == "panic-job" {
panic("任务执行失败")
}
fmt.Printf(" 任务 %s 完成\n", jobName)
}(job)
}
}
func simulateResourceManagement() {
resource := acquireResource()
defer func() {
releaseResource(resource)
if r := recover(); r != nil {
fmt.Printf(" 资源管理panic恢复: %v\n", r)
fmt.Printf(" 资源已安全释放\n")
}
}()
fmt.Printf(" 使用资源进行操作\n")
// 模拟操作中的panic
panic("操作过程中发生错误")
}
func acquireResource() string {
fmt.Printf(" 获取资源\n")
return "database-connection"
}
func releaseResource(resource string) {
fmt.Printf(" 释放资源: %s\n", resource)
}
go run panic_recover.go
panic和recover的最佳实践
何时使用panic
package main
import (
"fmt"
"log"
"os"
"time"
)
func main() {
fmt.Println("=== panic和recover最佳实践 ===")
// 演示何时使用panic
demonstrateWhenToPanic()
// 演示何时不使用panic
demonstrateWhenNotToPanic()
// 演示panic的替代方案
demonstrateAlternativesToPanic()
// 演示库设计中的panic使用
demonstrateLibraryPanicUsage()
}
func demonstrateWhenToPanic() {
fmt.Println("\n=== 何时使用panic ===")
fmt.Println("\n1. 程序初始化失败:")
// 模拟配置加载失败
defer func() {
if r := recover(); r != nil {
fmt.Printf(" 捕获初始化panic: %v\n", r)
}
}()
// initializeApp() // 这会panic,被上面的recover捕获
fmt.Println("\n2. 编程错误(断言失败):")
demonstrateAssertions()
fmt.Println("\n3. 不可能的情况:")
demonstrateImpossibleCases()
}
func initializeApp() {
// 模拟关键配置加载失败
if !loadCriticalConfig() {
panic("无法加载关键配置,程序无法继续运行")
}
}
func loadCriticalConfig() bool {
// 模拟配置加载失败
return false
}
func demonstrateAssertions() {
defer func() {
if r := recover(); r != nil {
fmt.Printf(" 断言失败: %v\n", r)
}
}()
// 使用断言检查编程错误
value := 42
assert(value > 0, "值必须为正数")
assert(value < 100, "值必须小于100")
assert(value == 50, "值必须等于50") // 这个断言会失败
}
func assert(condition bool, message string) {
if !condition {
panic(fmt.Sprintf("断言失败: %s", message))
}
}
func demonstrateImpossibleCases() {
defer func() {
if r := recover(); r != nil {
fmt.Printf(" 不可能情况: %v\n", r)
}
}()
status := getStatus()
switch status {
case "active":
fmt.Printf(" 状态: 活跃\n")
case "inactive":
fmt.Printf(" 状态: 非活跃\n")
case "pending":
fmt.Printf(" 状态: 待处理\n")
default:
// 理论上不应该到达这里
panic(fmt.Sprintf("未知状态: %s", status))
}
}
func getStatus() string {
return "unknown" // 模拟返回未知状态
}
func demonstrateWhenNotToPanic() {
fmt.Println("\n=== 何时不使用panic ===")
fmt.Println("\n1. 可预期的错误(应该使用error):")
// 好的做法:使用error
if err := processFile("nonexistent.txt"); err != nil {
fmt.Printf(" 文件处理错误: %v\n", err)
}
// 不好的做法:使用panic(这里用defer+recover演示)
func() {
defer func() {
if r := recover(); r != nil {
fmt.Printf(" 不当的panic使用: %v\n", r)
}
}()
processFileWithPanic("nonexistent.txt")
}()
fmt.Println("\n2. 网络错误(应该使用error):")
// 好的做法
if data, err := fetchData("http://example.com"); err != nil {
fmt.Printf(" 网络请求错误: %v\n", err)
} else {
fmt.Printf(" 获取数据: %s\n", data)
}
fmt.Println("\n3. 用户输入错误(应该使用error):")
// 好的做法
if err := validateUserInput(""); err != nil {
fmt.Printf(" 输入验证错误: %v\n", err)
}
}
func processFile(filename string) error {
if _, err := os.Stat(filename); os.IsNotExist(err) {
return fmt.Errorf("文件不存在: %s", filename)
}
return nil
}
func processFileWithPanic(filename string) {
if _, err := os.Stat(filename); os.IsNotExist(err) {
panic(fmt.Sprintf("文件不存在: %s", filename)) // 不好的做法
}
}
func fetchData(url string) (string, error) {
// 模拟网络请求
return "", fmt.Errorf("连接超时")
}
func validateUserInput(input string) error {
if input == "" {
return fmt.Errorf("输入不能为空")
}
return nil
}
func demonstrateAlternativesToPanic() {
fmt.Println("\n=== panic的替代方案 ===")
fmt.Println("\n1. 使用error返回:")
if result, err := safeCalculation(10, 0); err != nil {
fmt.Printf(" 计算错误: %v\n", err)
} else {
fmt.Printf(" 计算结果: %f\n", result)
}
fmt.Println("\n2. 使用bool返回:")
if result, ok := tryOperation(); !ok {
fmt.Printf(" 操作失败\n")
} else {
fmt.Printf(" 操作成功: %s\n", result)
}
fmt.Println("\n3. 使用Option模式:")
result := findUser("nonexistent")
if result.IsNone() {
fmt.Printf(" 用户不存在\n")
} else {
fmt.Printf(" 找到用户: %s\n", result.Value())
}
fmt.Println("\n4. 使用默认值:")
config := getConfigWithDefault("missing-key")
fmt.Printf(" 配置值: %s\n", config)
}
func safeCalculation(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为零")
}
return a / b, nil
}
func tryOperation() (string, bool) {
// 模拟可能失败的操作
success := false
if success {
return "操作结果", true
}
return "", false
}
// Option模式实现
type Option struct {
value string
hasValue bool
}
func Some(value string) Option {
return Option{value: value, hasValue: true}
}
func None() Option {
return Option{hasValue: false}
}
func (o Option) IsNone() bool {
return !o.hasValue
}
func (o Option) Value() string {
if !o.hasValue {
panic("试图获取None的值")
}
return o.value
}
func findUser(username string) Option {
users := map[string]string{
"alice": "Alice Smith",
"bob": "Bob Jones",
}
if name, exists := users[username]; exists {
return Some(name)
}
return None()
}
func getConfigWithDefault(key string) string {
config := map[string]string{
"host": "localhost",
"port": "8080",
}
if value, exists := config[key]; exists {
return value
}
return "default-value" // 返回默认值而不是panic
}
func demonstrateLibraryPanicUsage() {
fmt.Println("\n=== 库设计中的panic使用 ===")
fmt.Println("\n1. 提供panic和non-panic版本:")
// non-panic版本
if result, err := SafeParseInt("123"); err != nil {
fmt.Printf(" 安全解析失败: %v\n", err)
} else {
fmt.Printf(" 安全解析成功: %d\n", result)
}
// panic版本(用于确定输入有效的场景)
defer func() {
if r := recover(); r != nil {
fmt.Printf(" MustParseInt panic: %v\n", r)
}
}()
result1 := MustParseInt("456")
fmt.Printf(" MustParseInt成功: %d\n", result1)
result2 := MustParseInt("invalid") // 这会panic
fmt.Printf(" 这行不会执行: %d\n", result2)
}
func SafeParseInt(s string) (int, error) {
// 安全版本,返回error
if s == "invalid" {
return 0, fmt.Errorf("无效的整数格式: %s", s)
}
// 简化的解析逻辑
if s == "123" {
return 123, nil
}
if s == "456" {
return 456, nil
}
return 0, fmt.Errorf("无法解析: %s", s)
}
func MustParseInt(s string) int {
// panic版本,用于确定输入有效的场景
result, err := SafeParseInt(s)
if err != nil {
panic(fmt.Sprintf("MustParseInt失败: %v", err))
}
return result
}
go run panic_best_practices.go
错误处理模式
常见错误处理模式
错误聚合和批处理
package main
import (
"fmt"
"strings"
)
func main() {
fmt.Println("=== 错误处理模式 ===")
// 演示错误聚合
demonstrateErrorAggregation()
// 演示错误转换
demonstrateErrorTransformation()
// 演示错误上下文
demonstrateErrorContext()
// 演示错误分类处理
demonstrateErrorClassification()
}
// 错误聚合器
type ErrorAggregator struct {
errors []error
}
func NewErrorAggregator() *ErrorAggregator {
return &ErrorAggregator{}
}
func (ea *ErrorAggregator) Add(err error) {
if err != nil {
ea.errors = append(ea.errors, err)
}
}
func (ea *ErrorAggregator) HasErrors() bool {
return len(ea.errors) > 0
}
func (ea *ErrorAggregator) Error() string {
if len(ea.errors) == 0 {
return "没有错误"
}
var messages []string
for i, err := range ea.errors {
messages = append(messages, fmt.Sprintf("%d. %v", i+1, err))
}
return fmt.Sprintf("发现 %d 个错误:\n%s",
len(ea.errors), strings.Join(messages, "\n"))
}
func (ea *ErrorAggregator) Errors() []error {
return ea.errors
}
func demonstrateErrorAggregation() {
fmt.Println("\n=== 错误聚合 ===")
// 批量处理多个操作,收集所有错误
aggregator := NewErrorAggregator()
operations := []struct {
name string
fn func() error
}{
{"操作1", func() error { return validateInput("valid") }},
{"操作2", func() error { return validateInput("") }},
{"操作3", func() error { return validateInput("invalid") }},
{"操作4", func() error { return validateInput("valid2") }},
}
fmt.Println("执行批量操作:")
for _, op := range operations {
if err := op.fn(); err != nil {
fmt.Printf(" %s 失败: %v\n", op.name, err)
aggregator.Add(fmt.Errorf("%s: %w", op.name, err))
} else {
fmt.Printf(" %s 成功\n", op.name)
}
}
if aggregator.HasErrors() {
fmt.Printf("\n批量操作结果:\n%v\n", aggregator)
} else {
fmt.Printf("\n所有操作都成功\n")
}
}
func validateInput(input string) error {
if input == "" {
return fmt.Errorf("输入不能为空")
}
if input == "invalid" {
return fmt.Errorf("输入格式无效")
}
return nil
}
func demonstrateErrorTransformation() {
fmt.Println("\n=== 错误转换 ===")
// 演示将底层错误转换为业务错误
if err := businessOperation("user123"); err != nil {
fmt.Printf("业务操作失败: %v\n", err)
// 根据错误类型进行不同处理
if busErr, ok := err.(*BusinessError); ok {
fmt.Printf("错误代码: %s\n", busErr.Code)
fmt.Printf("用户消息: %s\n", busErr.UserMessage)
}
}
}
type BusinessError struct {
Code string
Message string
UserMessage string
Cause error
}
func (e *BusinessError) Error() string {
return fmt.Sprintf("[%s] %s", e.Code, e.Message)
}
func (e *BusinessError) Unwrap() error {
return e.Cause
}
func businessOperation(userID string) error {
// 模拟底层操作
if err := databaseQuery(userID); err != nil {
// 转换为业务错误
return &BusinessError{
Code: "USER_NOT_FOUND",
Message: fmt.Sprintf("用户查询失败: %v", err),
UserMessage: "用户不存在或已被删除",
Cause: err,
}
}
return nil
}
func databaseQuery(userID string) error {
if userID == "user123" {
return fmt.Errorf("数据库连接超时")
}
return nil
}
func demonstrateErrorContext() {
fmt.Println("\n=== 错误上下文 ===")
// 演示为错误添加上下文信息
ctx := &OperationContext{
UserID: "user456",
RequestID: "req-789",
Operation: "UpdateProfile",
}
if err := performOperation(ctx); err != nil {
fmt.Printf("操作失败: %v\n", err)
}
}
type OperationContext struct {
UserID string
RequestID string
Operation string
}
type ContextualError struct {
Context *OperationContext
Err error
}
func (e *ContextualError) Error() string {
return fmt.Sprintf("操作失败 [用户:%s, 请求:%s, 操作:%s]: %v",
e.Context.UserID, e.Context.RequestID, e.Context.Operation, e.Err)
}
func (e *ContextualError) Unwrap() error {
return e.Err
}
func performOperation(ctx *OperationContext) error {
if err := validateOperation(ctx); err != nil {
return &ContextualError{
Context: ctx,
Err: err,
}
}
return nil
}
func validateOperation(ctx *OperationContext) error {
if ctx.UserID == "user456" {
return fmt.Errorf("用户权限不足")
}
return nil
}
func demonstrateErrorClassification() {
fmt.Println("\n=== 错误分类处理 ===")
errors := []error{
&NetworkError{Message: "连接超时", Retryable: true},
&ValidationError{Field: "email", Message: "格式无效"},
&AuthError{Message: "认证失败", Code: "INVALID_TOKEN"},
&SystemError{Message: "磁盘空间不足", Critical: true},
}
for i, err := range errors {
fmt.Printf("\n错误 %d: %v\n", i+1, err)
handleClassifiedError(err)
}
}
// 错误分类接口
type RetryableError interface {
error
IsRetryable() bool
}
type CriticalError interface {
error
IsCritical() bool
}
// 网络错误
type NetworkError struct {
Message string
Retryable bool
}
func (e *NetworkError) Error() string {
return fmt.Sprintf("网络错误: %s", e.Message)
}
func (e *NetworkError) IsRetryable() bool {
return e.Retryable
}
// 验证错误
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("验证错误 [%s]: %s", e.Field, e.Message)
}
// 认证错误
type AuthError struct {
Message string
Code string
}
func (e *AuthError) Error() string {
return fmt.Sprintf("认证错误 [%s]: %s", e.Code, e.Message)
}
// 系统错误
type SystemError struct {
Message string
Critical bool
}
func (e *SystemError) Error() string {
return fmt.Sprintf("系统错误: %s", e.Message)
}
func (e *SystemError) IsCritical() bool {
return e.Critical
}
func handleClassifiedError(err error) {
// 检查是否可重试
if retryable, ok := err.(RetryableError); ok && retryable.IsRetryable() {
fmt.Printf(" 处理策略: 可重试错误,安排重试\n")
}
// 检查是否为关键错误
if critical, ok := err.(CriticalError); ok && critical.IsCritical() {
fmt.Printf(" 处理策略: 关键错误,立即通知管理员\n")
}
// 根据具体类型处理
switch e := err.(type) {
case *NetworkError:
fmt.Printf(" 处理策略: 网络错误,检查网络连接\n")
case *ValidationError:
fmt.Printf(" 处理策略: 验证错误,返回用户友好消息\n")
case *AuthError:
fmt.Printf(" 处理策略: 认证错误,重定向到登录页面\n")
case *SystemError:
fmt.Printf(" 处理策略: 系统错误,记录日志并监控\n")
default:
fmt.Printf(" 处理策略: 未知错误类型,使用默认处理\n")
}
}
go run error_patterns.go
总结
通过本章学习,你已经掌握了Go语言错误处理的核心概念和最佳实践:
关键要点
错误处理哲学
- 错误是值,不是异常
- 显式错误检查
- 简单、明确、可预测
error接口
- 简单的Error() string方法
- 自定义错误类型
- 错误包装和链式错误
最佳实践
- 总是检查错误
- 提供有意义的错误信息
- 选择合适的错误处理策略
- 正确传播错误
panic和recover
- 仅在无法恢复的情况下使用panic
- 使用recover进行资源清理
- 提供panic和non-panic版本的API
错误处理模式
- 错误聚合
- 错误转换
- 错误上下文
- 错误分类
下一步
下一章我们将学习Go语言的测试和调试,包括单元测试、基准测试、调试技巧和性能分析。这些技能将帮助你编写更可靠、更高性能的Go程序。