错误处理模式详解 - 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())
}::: ::: :::
🎯 核心知识点总结
错误处理基础要点
- error接口: Go的错误都实现了error接口,只有一个Error() string方法
- 显式检查: 必须显式检查每个可能返回错误的函数调用
- nil表示成功: error为nil表示没有错误发生
- 错误传播: 通过return将错误向上传播
错误包装要点
- fmt.Errorf %w: Go 1.13+使用%w进行错误包装
- errors.Is: 检查错误链中是否包含特定错误
- errors.As: 获取错误链中的特定类型错误
- errors.Unwrap: 解包获取原始错误
错误设计要点
- 结构化错误: 定义包含错误码、严重程度等信息的错误类型
- 错误分类: 区分可重试和不可重试错误
- 用户友好: 提供用户可理解的错误消息
- 可观测性: 支持错误监控和统计
最佳实践要点
- 早检查早返回: 尽早检查错误并处理
- 添加上下文: 包装错误时添加有用的上下文信息
- 重试策略: 对临时性错误实施重试机制
- 降级方案: 为关键服务准备降级策略
🔍 面试准备建议
- 掌握基本模式: 熟练掌握Go错误处理的基本模式和习惯用法
- 理解错误包装: 深入理解Go 1.13+的错误包装机制
- 设计错误类型: 能够设计合适的错误类型和错误层次结构
- 实践最佳实践: 了解生产环境中的错误处理最佳实践
- 性能考虑: 理解错误处理对性能的影响和优化方法
