Skip to content

错误处理模式详解 - Golang基础面试题

Go语言的错误处理是其设计哲学的重要体现,采用显式错误处理而非异常机制。本章深入探讨Go语言的错误处理模式和最佳实践。

📋 重点面试题

面试题 1:Go语言错误处理的基本概念

难度级别:⭐⭐⭐
考察范围:错误处理机制/接口设计
技术标签error interface explicit error handling error values nil error

问题分析

Go语言的错误处理机制基于error接口,这种设计让错误处理变得显式和可预测,是Go语言设计哲学的重要体现。

详细解答

1. error接口的基本概念

点击查看完整代码实现
点击查看完整代码实现
go
package main

import (
    "fmt"
    "errors"
    "io"
    "os"
)

// Go内置的error接口
// type error interface {
//     Error() string
// }

func demonstrateBasicErrorHandling() {
    fmt.Println("=== 基本错误处理 ===")
    
    // 1. 使用errors.New创建错误
    err1 := errors.New("这是一个简单的错误")
    fmt.Printf("err1: %v\n", err1)
    fmt.Printf("err1类型: %T\n", err1)
    
    // 2. 使用fmt.Errorf创建格式化错误
    name := "test.txt"
    err2 := fmt.Errorf("无法打开文件 %s: 文件不存在", name)
    fmt.Printf("err2: %v\n", err2)
    
    // 3. nil错误表示没有错误
    var err3 error = nil
    fmt.Printf("err3 == nil: %t\n", err3 == nil)
    
    // 4. 函数返回错误的基本模式
    result, err := divide(10, 2)
    if err != nil {
        fmt.Printf("除法错误: %v\n", err)
    } else {
        fmt.Printf("除法结果: %.2f\n", result)
    }
    
    result, err = divide(10, 0)
    if err != nil {
        fmt.Printf("除法错误: %v\n", err)
    } else {
        fmt.Printf("除法结果: %.2f\n", result)
    }
}

// 返回错误的函数示例
func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("除数不能为零")
    }
    return a / b, nil
}

:::

2. 错误检查的常见模式

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
func demonstrateErrorCheckingPatterns() {
    fmt.Println("\n=== 错误检查模式 ===")
    
    // 模式1:立即检查并处理
    file, err := os.Open("nonexistent.txt")
    if err != nil {
        fmt.Printf("立即处理错误: %v\n", err)
        // 可以选择返回错误、记录日志、使用默认值等
    } else {
        defer file.Close()
        fmt.Println("文件打开成功")
    }
    
    // 模式2:错误传播
    if err := processFile("test.txt"); err != nil {
        fmt.Printf("处理文件失败: %v\n", err)
    }
    
    // 模式3:错误包装和上下文
    if err := complexOperation(); err != nil {
        fmt.Printf("复杂操作失败: %v\n", err)
    }
    
    // 模式4:多重错误检查
    content, err := readAndValidateFile("config.json")
    if err != nil {
        fmt.Printf("读取和验证文件失败: %v\n", err)
    } else {
        fmt.Printf("文件内容长度: %d\n", len(content))
    }
}

func processFile(filename string) error {
    file, err := os.Open(filename)
    if err != nil {
        // 包装错误,添加上下文信息
        return fmt.Errorf("无法打开文件 %s: %w", filename, err)
    }
    defer file.Close()
    
    // 处理文件...
    return nil
}

func complexOperation() error {
    // 步骤1
    if err := step1(); err != nil {
        return fmt.Errorf("步骤1失败: %w", err)
    }
    
    // 步骤2
    if err := step2(); err != nil {
        return fmt.Errorf("步骤2失败: %w", err)
    }
    
    // 步骤3
    if err := step3(); err != nil {
        return fmt.Errorf("步骤3失败: %w", err)
    }
    
    return nil
}

func step1() error { return errors.New("步骤1内部错误") }
func step2() error { return nil }
func step3() error { return errors.New("步骤3内部错误") }

func readAndValidateFile(filename string) ([]byte, error) {
    // 读取文件
    content, err := os.ReadFile(filename)
    if err != nil {
        return nil, fmt.Errorf("读取文件失败: %w", err)
    }
    
    // 验证内容
    if len(content) == 0 {
        return nil, errors.New("文件内容为空")
    }
    
    if len(content) > 1024*1024 {  // 1MB限制
        return nil, errors.New("文件过大")
    }
    
    return content, nil
}

::: ::: :::

3. 错误类型判断和处理

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
import (
    "net"
    "syscall"
    "time"
)

func demonstrateErrorTypeChecking() {
    fmt.Println("\n=== 错误类型检查 ===")
    
    // 1. 错误值比较
    _, err := os.Open("nonexistent.txt")
    if err != nil {
        // 使用errors.Is进行错误比较
        if errors.Is(err, os.ErrNotExist) {
            fmt.Println("文件不存在")
        } else if errors.Is(err, os.ErrPermission) {
            fmt.Println("权限不足")
        } else {
            fmt.Printf("其他错误: %v\n", err)
        }
    }
    
    // 2. 网络错误处理
    conn, err := net.DialTimeout("tcp", "nonexistent:80", time.Second)
    if err != nil {
        handleNetworkError(err)
    } else {
        conn.Close()
    }
    
    // 3. 系统调用错误
    err = syscall.Kill(99999, syscall.SIGTERM)  // 假设进程不存在
    if err != nil {
        handleSystemError(err)
    }
}

func handleNetworkError(err error) {
    fmt.Printf("网络错误: %v\n", err)
    
    // 检查是否是超时错误
    if netErr, ok := err.(net.Error); ok {
        if netErr.Timeout() {
            fmt.Println("  -> 网络超时")
        }
        if netErr.Temporary() {
            fmt.Println("  -> 临时网络错误,可以重试")
        }
    }
    
    // 检查DNS错误
    if dnsErr, ok := err.(*net.DNSError); ok {
        fmt.Printf("  -> DNS错误: %s\n", dnsErr.Name)
        if dnsErr.IsNotFound {
            fmt.Println("  -> 域名未找到")
        }
    }
    
    // 检查地址错误
    if addrErr, ok := err.(*net.AddrError); ok {
        fmt.Printf("  -> 地址错误: %s\n", addrErr.Addr)
    }
}

func handleSystemError(err error) {
    fmt.Printf("系统错误: %v\n", err)
    
    // 检查是否是系统调用错误
    if errno, ok := err.(syscall.Errno); ok {
        switch errno {
        case syscall.ENOENT:
            fmt.Println("  -> 文件或目录不存在")
        case syscall.EACCES:
            fmt.Println("  -> 权限被拒绝")
        case syscall.ESRCH:
            fmt.Println("  -> 进程不存在")
        default:
            fmt.Printf("  -> 系统错误码: %d\n", errno)
        }
    }
}

::: ::: :::

面试题 2:错误包装和错误链

难度级别:⭐⭐⭐⭐
考察范围:错误包装/Go 1.13+新特性
技术标签error wrapping errors.Unwrap errors.Is errors.As fmt.Errorf %w

问题分析

Go 1.13引入了错误包装机制,允许在保留原始错误的同时添加上下文信息,这是现代Go错误处理的重要特性。

详细解答

1. 错误包装的基本使用

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
import (
    "database/sql"
    "encoding/json"
)

// 自定义错误类型
type ValidationError struct {
    Field   string
    Value   interface{}
    Message string
    Err     error  // 包装的原始错误
}

func (ve *ValidationError) Error() string {
    return fmt.Sprintf("validation failed for field '%s' with value '%v': %s", 
        ve.Field, ve.Value, ve.Message)
}

func (ve *ValidationError) Unwrap() error {
    return ve.Err
}

type DatabaseError struct {
    Operation string
    Query     string
    Err       error
}

func (de *DatabaseError) Error() string {
    return fmt.Sprintf("database operation '%s' failed: %s", de.Operation, de.Err.Error())
}

func (de *DatabaseError) Unwrap() error {
    return de.Err
}

func demonstrateErrorWrapping() {
    fmt.Println("\n=== 错误包装 ===")
    
    // 1. 使用fmt.Errorf包装错误
    originalErr := errors.New("原始错误")
    wrappedErr := fmt.Errorf("包装错误: %w", originalErr)
    
    fmt.Printf("原始错误: %v\n", originalErr)
    fmt.Printf("包装错误: %v\n", wrappedErr)
    
    // 2. 使用errors.Unwrap解包
    unwrapped := errors.Unwrap(wrappedErr)
    fmt.Printf("解包后: %v\n", unwrapped)
    fmt.Printf("解包相等: %t\n", unwrapped == originalErr)
    
    // 3. 多层包装
    layer1 := fmt.Errorf("第一层包装: %w", originalErr)
    layer2 := fmt.Errorf("第二层包装: %w", layer1)
    layer3 := fmt.Errorf("第三层包装: %w", layer2)
    
    fmt.Printf("多层包装: %v\n", layer3)
    
    // 4. 使用errors.Is检查错误链
    fmt.Printf("errors.Is(layer3, originalErr): %t\n", 
        errors.Is(layer3, originalErr))
    
    // 5. 自定义错误类型的包装
    validationErr := &ValidationError{
        Field:   "email",
        Value:   "invalid-email",
        Message: "invalid email format",
        Err:     errors.New("格式错误"),
    }
    
    serviceErr := fmt.Errorf("用户服务错误: %w", validationErr)
    fmt.Printf("服务错误: %v\n", serviceErr)
    
    // 检查错误类型
    var ve *ValidationError
    if errors.As(serviceErr, &ve) {
        fmt.Printf("找到ValidationError: field=%s, value=%v\n", 
            ve.Field, ve.Value)
    }
}

::: ::: :::

2. errors.Is和errors.As的使用

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
var (
    ErrUserNotFound    = errors.New("用户未找到")
    ErrInvalidPassword = errors.New("密码无效")
    ErrAccountLocked   = errors.New("账户被锁定")
)

func demonstrateErrorsIsAs() {
    fmt.Println("\n=== errors.Is和errors.As ===")
    
    // 模拟用户认证错误
    authErr := authenticateUser("user123", "wrongpass")
    
    // 使用errors.Is检查具体错误
    if errors.Is(authErr, ErrUserNotFound) {
        fmt.Println("用户不存在,需要注册")
    } else if errors.Is(authErr, ErrInvalidPassword) {
        fmt.Println("密码错误,请重试")
    } else if errors.Is(authErr, ErrAccountLocked) {
        fmt.Println("账户被锁定,请联系管理员")
    } else if authErr != nil {
        fmt.Printf("其他认证错误: %v\n", authErr)
    }
    
    // 模拟数据库操作错误
    dbErr := queryUser("user123")
    
    // 使用errors.As获取具体错误类型
    var dbError *DatabaseError
    if errors.As(dbErr, &dbError) {
        fmt.Printf("数据库错误详情:\n")
        fmt.Printf("  操作: %s\n", dbError.Operation)
        fmt.Printf("  查询: %s\n", dbError.Query)
        
        // 进一步检查底层错误
        if errors.Is(dbError.Err, sql.ErrNoRows) {
            fmt.Println("  -> 查询结果为空")
        }
    }
    
    // 网络错误处理示例
    netErr := makeNetworkRequest("https://api.example.com/users")
    handleNetworkErrorAdvanced(netErr)
}

func authenticateUser(username, password string) error {
    // 模拟认证逻辑
    if username == "nonexistent" {
        return fmt.Errorf("认证失败: %w", ErrUserNotFound)
    }
    if password == "wrongpass" {
        return fmt.Errorf("认证失败: %w", ErrInvalidPassword)
    }
    if username == "locked" {
        return fmt.Errorf("认证失败: %w", ErrAccountLocked)
    }
    return nil
}

func queryUser(userID string) error {
    // 模拟数据库查询
    dbErr := &DatabaseError{
        Operation: "SELECT",
        Query:     "SELECT * FROM users WHERE id = ?",
        Err:       sql.ErrNoRows,
    }
    return fmt.Errorf("数据库查询失败: %w", dbErr)
}

func makeNetworkRequest(url string) error {
    // 模拟网络请求错误
    return fmt.Errorf("HTTP请求失败: %w", 
        &net.DNSError{
            Err:        "no such host",
            Name:       "api.example.com",
            IsNotFound: true,
        })
}

func handleNetworkErrorAdvanced(err error) {
    if err == nil {
        return
    }
    
    fmt.Printf("处理网络错误: %v\n", err)
    
    // 检查DNS错误
    var dnsErr *net.DNSError
    if errors.As(err, &dnsErr) {
        fmt.Printf("  DNS错误: %s\n", dnsErr.Name)
        if dnsErr.IsNotFound {
            fmt.Println("  -> 建议: 检查域名是否正确")
        }
        return
    }
    
    // 检查网络错误接口
    var netErr net.Error
    if errors.As(err, &netErr) {
        if netErr.Timeout() {
            fmt.Println("  -> 网络超时,建议重试")
        }
        if netErr.Temporary() {
            fmt.Println("  -> 临时错误,稍后重试")
        }
        return
    }
    
    fmt.Println("  -> 未知网络错误")
}

::: ::: :::

3. 错误收集和批处理

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
// 错误收集器
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 ""
    }
    
    var messages []string
    for i, err := range ec.errors {
        messages = append(messages, fmt.Sprintf("%d: %s", i+1, err.Error()))
    }
    return fmt.Sprintf("collected %d errors:\n%s", 
        len(ec.errors), strings.Join(messages, "\n"))
}

func (ec *ErrorCollector) Errors() []error {
    return ec.errors
}

// 多重错误类型
type MultiError []error

func (me MultiError) Error() string {
    if len(me) == 0 {
        return ""
    }
    if len(me) == 1 {
        return me[0].Error()
    }
    
    var buf strings.Builder
    buf.WriteString(fmt.Sprintf("%d errors occurred:", len(me)))
    for i, err := range me {
        buf.WriteString(fmt.Sprintf("\n\t%d: %s", i+1, err.Error()))
    }
    return buf.String()
}

func demonstrateErrorCollection() {
    fmt.Println("\n=== 错误收集 ===")
    
    // 1. 使用ErrorCollector
    collector := &ErrorCollector{}
    
    // 模拟多个操作,可能产生错误
    operations := []func() error{
        func() error { return validateEmail("invalid-email") },
        func() error { return validateAge(-5) },
        func() error { return validateName("") },
        func() error { return validatePhone("123") },
    }
    
    for i, op := range operations {
        err := op()
        if err != nil {
            collector.Add(fmt.Errorf("操作%d失败: %w", i+1, err))
        }
    }
    
    if collector.HasErrors() {
        fmt.Printf("收集到的错误:\n%s\n", collector.Error())
    }
    
    // 2. 使用MultiError
    var multiErr MultiError
    
    // 批量处理,收集所有错误
    items := []string{"item1", "invalid", "item3", ""}
    for i, item := range items {
        if err := processItem(item); err != nil {
            multiErr = append(multiErr, fmt.Errorf("处理item[%d]失败: %w", i, err))
        }
    }
    
    if len(multiErr) > 0 {
        fmt.Printf("批处理错误:\n%s\n", multiErr.Error())
    }
    
    // 3. 并发错误收集
    demonstrateConcurrentErrorCollection()
}

func validateEmail(email string) error {
    if !strings.Contains(email, "@") {
        return errors.New("邮箱格式无效")
    }
    return nil
}

func validateAge(age int) error {
    if age < 0 {
        return errors.New("年龄不能为负数")
    }
    if age > 150 {
        return errors.New("年龄不能超过150")
    }
    return nil
}

func validateName(name string) error {
    if name == "" {
        return errors.New("姓名不能为空")
    }
    return nil
}

func validatePhone(phone string) error {
    if len(phone) < 10 {
        return errors.New("电话号码太短")
    }
    return nil
}

func processItem(item string) error {
    if item == "" {
        return errors.New("项目不能为空")
    }
    if item == "invalid" {
        return errors.New("无效的项目")
    }
    return nil
}

// 并发错误收集
func demonstrateConcurrentErrorCollection() {
    fmt.Println("\n=== 并发错误收集 ===")
    
    const numWorkers = 5
    items := make(chan string, 10)
    errChan := make(chan error, 10)
    
    // 启动worker
    for i := 0; i < numWorkers; i++ {
        go func(workerID int) {
            for item := range items {
                if err := processItemWithDelay(item, workerID); err != nil {
                    errChan <- fmt.Errorf("worker %d: %w", workerID, err)
                }
            }
        }(i)
    }
    
    // 发送任务
    testItems := []string{"good1", "bad", "good2", "", "invalid", "good3"}
    go func() {
        for _, item := range testItems {
            items <- item
        }
        close(items)
    }()
    
    // 收集错误
    var collectedErrors []error
    timeout := time.After(2 * time.Second)
    
    for {
        select {
        case err := <-errChan:
            collectedErrors = append(collectedErrors, err)
        case <-timeout:
            goto done
        }
    }
    
done:
    if len(collectedErrors) > 0 {
        multiErr := MultiError(collectedErrors)
        fmt.Printf("并发处理错误:\n%s\n", multiErr.Error())
    } else {
        fmt.Println("并发处理完成,无错误")
    }
}

func processItemWithDelay(item string, workerID int) error {
    time.Sleep(100 * time.Millisecond)  // 模拟处理时间
    return processItem(item)
}

::: ::: :::

面试题 3:错误处理的最佳实践

难度级别:⭐⭐⭐⭐⭐
考察范围:设计模式/代码质量
技术标签best practices error design logging monitoring graceful degradation

问题分析

良好的错误处理设计是生产级Go应用的关键,需要考虑可观测性、用户体验、系统稳定性等多个方面。

详细解答

1. 错误处理的设计原则

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
import (
    "context"
    "log"
    "time"
)

// 错误分类
type ErrorSeverity int

const (
    ErrorSeverityInfo ErrorSeverity = iota
    ErrorSeverityWarning
    ErrorSeverityError
    ErrorSeverityCritical
)

// 业务错误接口
type BusinessError interface {
    error
    Code() string
    Severity() ErrorSeverity
    IsRetryable() bool
    UserMessage() string
}

// 具体业务错误实现
type ServiceError struct {
    ErrCode      string
    ErrMessage   string
    ErrSeverity  ErrorSeverity
    ErrRetryable bool
    ErrUserMsg   string
    ErrCause     error
}

func (se *ServiceError) Error() string {
    if se.ErrCause != nil {
        return fmt.Sprintf("%s: %s (caused by: %v)", se.ErrCode, se.ErrMessage, se.ErrCause)
    }
    return fmt.Sprintf("%s: %s", se.ErrCode, se.ErrMessage)
}

func (se *ServiceError) Code() string           { return se.ErrCode }
func (se *ServiceError) Severity() ErrorSeverity { return se.ErrSeverity }
func (se *ServiceError) IsRetryable() bool      { return se.ErrRetryable }
func (se *ServiceError) UserMessage() string    { return se.ErrUserMsg }
func (se *ServiceError) Unwrap() error          { return se.ErrCause }

// 错误构建器
type ErrorBuilder struct {
    err *ServiceError
}

func NewError(code string) *ErrorBuilder {
    return &ErrorBuilder{
        err: &ServiceError{
            ErrCode:     code,
            ErrSeverity: ErrorSeverityError,
        },
    }
}

func (eb *ErrorBuilder) Message(msg string) *ErrorBuilder {
    eb.err.ErrMessage = msg
    return eb
}

func (eb *ErrorBuilder) Severity(severity ErrorSeverity) *ErrorBuilder {
    eb.err.ErrSeverity = severity
    return eb
}

func (eb *ErrorBuilder) Retryable(retryable bool) *ErrorBuilder {
    eb.err.ErrRetryable = retryable
    return eb
}

func (eb *ErrorBuilder) UserMessage(msg string) *ErrorBuilder {
    eb.err.ErrUserMsg = msg
    return eb
}

func (eb *ErrorBuilder) Cause(cause error) *ErrorBuilder {
    eb.err.ErrCause = cause
    return eb
}

func (eb *ErrorBuilder) Build() *ServiceError {
    return eb.err
}

func demonstrateErrorDesignPrinciples() {
    fmt.Println("\n=== 错误设计原则 ===")
    
    // 1. 构建结构化错误
    userNotFoundErr := NewError("USER_NOT_FOUND").
        Message("指定的用户ID不存在").
        Severity(ErrorSeverityWarning).
        Retryable(false).
        UserMessage("用户不存在,请检查用户ID").
        Build()
    
    // 2. 包装底层错误
    dbConnErr := errors.New("connection refused")
    dbErr := NewError("DATABASE_CONNECTION_FAILED").
        Message("数据库连接失败").
        Severity(ErrorSeverityCritical).
        Retryable(true).
        UserMessage("服务暂时不可用,请稍后重试").
        Cause(dbConnErr).
        Build()
    
    // 3. 错误处理和响应
    handleBusinessError(userNotFoundErr)
    handleBusinessError(dbErr)
}

func handleBusinessError(err error) {
    if err == nil {
        return
    }
    
    var businessErr BusinessError
    if errors.As(err, &businessErr) {
        // 记录日志
        logLevel := getLogLevel(businessErr.Severity())
        log.Printf("[%s] %s - %s", logLevel, businessErr.Code(), businessErr.Error())
        
        // 判断是否需要重试
        if businessErr.IsRetryable() {
            fmt.Printf("错误可重试: %s\n", businessErr.UserMessage())
        } else {
            fmt.Printf("错误不可重试: %s\n", businessErr.UserMessage())
        }
        
        // 根据严重程度采取不同措施
        switch businessErr.Severity() {
        case ErrorSeverityCritical:
            fmt.Println("  -> 发送告警通知")
            fmt.Println("  -> 启动降级策略")
        case ErrorSeverityError:
            fmt.Println("  -> 记录错误日志")
        case ErrorSeverityWarning:
            fmt.Println("  -> 记录警告日志")
        }
    } else {
        fmt.Printf("未分类错误: %v\n", err)
    }
}

func getLogLevel(severity ErrorSeverity) string {
    switch severity {
    case ErrorSeverityInfo:
        return "INFO"
    case ErrorSeverityWarning:
        return "WARN"
    case ErrorSeverityError:
        return "ERROR"
    case ErrorSeverityCritical:
        return "CRITICAL"
    default:
        return "UNKNOWN"
    }
}

::: ::: :::

2. 重试和降级策略

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
// 重试配置
type RetryConfig struct {
    MaxAttempts int
    BaseDelay   time.Duration
    MaxDelay    time.Duration
    Multiplier  float64
}

// 重试器
func WithRetry(config RetryConfig, operation func() error) error {
    var lastErr error
    
    for attempt := 1; attempt <= config.MaxAttempts; attempt++ {
        err := operation()
        if err == nil {
            return nil
        }
        
        lastErr = err
        
        // 检查是否应该重试
        var businessErr BusinessError
        if errors.As(err, &businessErr) && !businessErr.IsRetryable() {
            return err  // 不可重试的错误,直接返回
        }
        
        if attempt == config.MaxAttempts {
            break  // 最后一次尝试
        }
        
        // 计算延迟时间(指数退避)
        delay := time.Duration(float64(config.BaseDelay) * 
            math.Pow(config.Multiplier, float64(attempt-1)))
        if delay > config.MaxDelay {
            delay = config.MaxDelay
        }
        
        fmt.Printf("重试 %d/%d 失败: %v (等待 %v)\n", 
            attempt, config.MaxAttempts, err, delay)
        
        time.Sleep(delay)
    }
    
    return fmt.Errorf("重试 %d 次后仍然失败: %w", config.MaxAttempts, lastErr)
}

// 断路器模式
type CircuitBreaker struct {
    maxFailures  int
    resetTimeout time.Duration
    failures     int
    lastFailTime time.Time
    state        string // "closed", "open", "half-open"
}

func NewCircuitBreaker(maxFailures int, resetTimeout time.Duration) *CircuitBreaker {
    return &CircuitBreaker{
        maxFailures:  maxFailures,
        resetTimeout: resetTimeout,
        state:        "closed",
    }
}

func (cb *CircuitBreaker) Execute(operation func() error) error {
    if cb.state == "open" {
        if time.Since(cb.lastFailTime) > cb.resetTimeout {
            cb.state = "half-open"
            cb.failures = 0
        } else {
            return errors.New("断路器开启,服务不可用")
        }
    }
    
    err := operation()
    
    if err != nil {
        cb.failures++
        cb.lastFailTime = time.Now()
        
        if cb.failures >= cb.maxFailures {
            cb.state = "open"
        }
        return err
    }
    
    // 成功时重置
    cb.failures = 0
    cb.state = "closed"
    return nil
}

func demonstrateRetryAndFallback() {
    fmt.Println("\n=== 重试和降级策略 ===")
    
    // 1. 重试机制
    retryConfig := RetryConfig{
        MaxAttempts: 3,
        BaseDelay:   100 * time.Millisecond,
        MaxDelay:    1 * time.Second,
        Multiplier:  2.0,
    }
    
    // 模拟不稳定的操作
    attempts := 0
    unstableOperation := func() error {
        attempts++
        if attempts < 3 {
            return NewError("TEMPORARY_FAILURE").
                Message("临时失败").
                Retryable(true).
                Build()
        }
        return nil
    }
    
    err := WithRetry(retryConfig, unstableOperation)
    if err != nil {
        fmt.Printf("重试失败: %v\n", err)
    } else {
        fmt.Println("重试成功")
    }
    
    // 2. 断路器模式
    cb := NewCircuitBreaker(2, 5*time.Second)
    
    // 模拟连续失败
    for i := 0; i < 5; i++ {
        err := cb.Execute(func() error {
            return errors.New("服务错误")
        })
        fmt.Printf("断路器测试 %d: %v (状态: %s)\n", i+1, err, cb.state)
    }
    
    // 3. 降级策略
    result, err := WithFallback(
        func() (interface{}, error) {
            return nil, errors.New("主服务失败")
        },
        func() (interface{}, error) {
            return "降级数据", nil
        },
    )
    
    if err != nil {
        fmt.Printf("降级也失败: %v\n", err)
    } else {
        fmt.Printf("降级成功: %v\n", result)
    }
}

func WithFallback(primary func() (interface{}, error), fallback func() (interface{}, error)) (interface{}, error) {
    result, err := primary()
    if err != nil {
        fmt.Printf("主操作失败,启用降级: %v\n", err)
        return fallback()
    }
    return result, nil
}

::: ::: :::

3. 错误监控和可观测性

点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
点击查看完整代码实现
go
import (
    "encoding/json"
    "sync"
)

// 错误统计
type ErrorStats struct {
    mu     sync.RWMutex
    counts map[string]int
    rates  map[string]float64
}

func NewErrorStats() *ErrorStats {
    return &ErrorStats{
        counts: make(map[string]int),
        rates:  make(map[string]float64),
    }
}

func (es *ErrorStats) RecordError(code string) {
    es.mu.Lock()
    defer es.mu.Unlock()
    es.counts[code]++
}

func (es *ErrorStats) GetStats() map[string]int {
    es.mu.RLock()
    defer es.mu.RUnlock()
    
    result := make(map[string]int)
    for k, v := range es.counts {
        result[k] = v
    }
    return result
}

// 错误监控器
type ErrorMonitor struct {
    stats     *ErrorStats
    thresholds map[string]int
    alerts    chan Alert
}

type Alert struct {
    ErrorCode string    `json:"error_code"`
    Count     int       `json:"count"`
    Threshold int       `json:"threshold"`
    Timestamp time.Time `json:"timestamp"`
}

func NewErrorMonitor() *ErrorMonitor {
    return &ErrorMonitor{
        stats:     NewErrorStats(),
        thresholds: make(map[string]int),
        alerts:    make(chan Alert, 100),
    }
}

func (em *ErrorMonitor) SetThreshold(errorCode string, threshold int) {
    em.thresholds[errorCode] = threshold
}

func (em *ErrorMonitor) RecordError(err error) {
    var businessErr BusinessError
    if errors.As(err, &businessErr) {
        code := businessErr.Code()
        em.stats.RecordError(code)
        
        // 检查阈值
        if threshold, exists := em.thresholds[code]; exists {
            currentCount := em.stats.counts[code]
            if currentCount >= threshold {
                alert := Alert{
                    ErrorCode: code,
                    Count:     currentCount,
                    Threshold: threshold,
                    Timestamp: time.Now(),
                }
                
                select {
                case em.alerts <- alert:
                default:
                    // 告警队列满,丢弃
                }
            }
        }
    }
}

func (em *ErrorMonitor) GetAlerts() <-chan Alert {
    return em.alerts
}

func (em *ErrorMonitor) GetStatsSummary() string {
    stats := em.stats.GetStats()
    data, _ := json.MarshalIndent(stats, "", "  ")
    return string(data)
}

func demonstrateErrorMonitoring() {
    fmt.Println("\n=== 错误监控 ===")
    
    monitor := NewErrorMonitor()
    
    // 设置阈值
    monitor.SetThreshold("DATABASE_ERROR", 3)
    monitor.SetThreshold("NETWORK_ERROR", 5)
    
    // 启动告警处理
    go func() {
        for alert := range monitor.GetAlerts() {
            fmt.Printf("🚨 告警: 错误 %s 达到阈值 (当前: %d, 阈值: %d) at %v\n",
                alert.ErrorCode, alert.Count, alert.Threshold, alert.Timestamp.Format("15:04:05"))
        }
    }()
    
    // 模拟错误产生
    errors := []error{
        NewError("DATABASE_ERROR").Message("数据库连接失败").Build(),
        NewError("NETWORK_ERROR").Message("网络超时").Build(),
        NewError("DATABASE_ERROR").Message("查询失败").Build(),
        NewError("VALIDATION_ERROR").Message("参数无效").Build(),
        NewError("DATABASE_ERROR").Message("事务失败").Build(),
        NewError("NETWORK_ERROR").Message("DNS解析失败").Build(),
        NewError("DATABASE_ERROR").Message("连接池满").Build(), // 这个会触发告警
    }
    
    for i, err := range errors {
        monitor.RecordError(err)
        fmt.Printf("记录错误 %d: %s\n", i+1, err.Error())
        time.Sleep(100 * time.Millisecond)
    }
    
    // 等待告警处理
    time.Sleep(500 * time.Millisecond)
    
    // 打印统计信息
    fmt.Printf("\n错误统计:\n%s\n", monitor.GetStatsSummary())
}

::: ::: :::

🎯 核心知识点总结

错误处理基础要点

  1. error接口: Go的错误都实现了error接口,只有一个Error() string方法
  2. 显式检查: 必须显式检查每个可能返回错误的函数调用
  3. nil表示成功: error为nil表示没有错误发生
  4. 错误传播: 通过return将错误向上传播

错误包装要点

  1. fmt.Errorf %w: Go 1.13+使用%w进行错误包装
  2. errors.Is: 检查错误链中是否包含特定错误
  3. errors.As: 获取错误链中的特定类型错误
  4. errors.Unwrap: 解包获取原始错误

错误设计要点

  1. 结构化错误: 定义包含错误码、严重程度等信息的错误类型
  2. 错误分类: 区分可重试和不可重试错误
  3. 用户友好: 提供用户可理解的错误消息
  4. 可观测性: 支持错误监控和统计

最佳实践要点

  1. 早检查早返回: 尽早检查错误并处理
  2. 添加上下文: 包装错误时添加有用的上下文信息
  3. 重试策略: 对临时性错误实施重试机制
  4. 降级方案: 为关键服务准备降级策略

🔍 面试准备建议

  1. 掌握基本模式: 熟练掌握Go错误处理的基本模式和习惯用法
  2. 理解错误包装: 深入理解Go 1.13+的错误包装机制
  3. 设计错误类型: 能够设计合适的错误类型和错误层次结构
  4. 实践最佳实践: 了解生产环境中的错误处理最佳实践
  5. 性能考虑: 理解错误处理对性能的影响和优化方法

正在精进