Go08-错误处理

📅 2026/7/16 21:37:08 👁️ 阅读次数 📝 编程学习
Go08-错误处理

第7章:错误处理

7.1 错误处理基础

Go 使用error接口处理错误,而非异常机制。

error 接口

// error 是一个内置接口typeerrorinterface{Error()string}// 创建错误err:=errors.New("something went wrong")fmt.Println(err)// something went wrong

惯用错误处理模式

// Go 的标准错误处理模式result,err:=doSomething()iferr!=nil{// 处理错误fmt.Println("错误:",err)returnerr}// 继续正常处理...

多返回值错误

funcdivide(a,bfloat64)(float64,error){ifb==0{return0,errors.New("除数不能为零")}returna/b,nil}// 使用result,err:=divide(10,0)iferr!=nil{fmt.Println("错误:",err)return}fmt.Println("结果:",result)

7.2 自定义错误

使用 errors.New

funcvalidateAge(ageint)error{ifage<0||age>150{returnerrors.New("年龄必须在 0-150 之间")}returnnil}

自定义错误类型

// 定义错误类型typeValidationErrorstruct{FieldstringMessagestring}func(e*ValidationError)Error()string{returnfmt.Sprintf("验证错误: 字段 %s, %s",e.Field,e.Message)}// 使用funcvalidateEmail(emailstring)error{ifemail==""{return&ValidationError{Field:"email",Message:"不能为空",}}if!strings.Contains(email,"@"){return&ValidationError{Field:"email",Message:"格式不正确",}}returnnil}// 处理自定义错误err:=validateEmail("invalid")iferr!=nil{varve*ValidationErroriferrors.As(err,&ve){fmt.Printf("字段: %s, 错误: %s\n",ve.Field,ve.Message)}}

错误包装

// Go 1.13+ 支持错误包装funcreadFile(filenamestring)([]byte,error){data,err:=os.ReadFile(filename)iferr!=nil{// 使用 fmt.Errorf 包装错误returnnil,fmt.Errorf("读取文件失败: %w",err)}returndata,nil}// 检查包装的错误err:=readFile("nonexistent.txt")iferr!=nil{// errors.Is 检查错误链iferrors.Is(err,os.ErrNotExist){fmt.Println("文件不存在")}// errors.As 提取特定错误类型varpathError*os.PathErroriferrors.As(err,&pathError){fmt.Println("路径错误:",pathError.Path)}}

7.3 错误处理最佳实践

1. 错误应该被处理

// 不推荐:忽略错误data,_:=os.ReadFile("file.txt")// 推荐:处理错误data,err:=os.ReadFile("file.txt")iferr!=nil{returnfmt.Errorf("读取文件失败: %w",err)}

2. 错误信息应该小写

// 不推荐returnerrors.New("Something Went Wrong")// 推荐returnerrors.New("something went wrong")

3. 错误应该提供上下文

// 不推荐returnerrors.New("failed")// 推荐returnfmt.Errorf("failed to read config file %s: %w",filename,err)

4. 使用哨兵错误

// 定义哨兵错误var(ErrNotFound=errors.New("not found")ErrUnauthorized=errors.New("unauthorized")ErrForbidden=errors.New("forbidden"))// 使用funcfindUser(idint)(*User,error){user:=db.FindByID(id)ifuser==nil{returnnil,ErrNotFound}returnuser,nil}// 检查user,err:=findUser(123)iferr!=nil{switch{caseerrors.Is(err,ErrNotFound):fmt.Println("用户不存在")caseerrors.Is(err,ErrUnauthorized):fmt.Println("未授权")default:fmt.Println("未知错误:",err)}}

7.4 panic 与 recover

panic

// panic 用于不可恢复的错误funcdivide(a,bint)int{ifb==0{panic("除数不能为零")}returna/b}// 捕获 panicfuncsafeDivide(a,bint)(resultint,errerror){deferfunc(){ifr:=recover();r!=nil{err=fmt.Errorf("panic: %v",r)}}()returndivide(a,b),nil}

recover

// recover 用于捕获 panicfuncmain(){deferfunc(){ifr:=recover();r!=nil{fmt.Println("捕获 panic:",r)}}()// 可能 panic 的代码panic("something bad happened")}

使用场景

// 1. HTTP 服务器中的 panic 恢复funcrecoveryMiddleware(next http.Handler)http.Handler{returnhttp.HandlerFunc(func(w http.ResponseWriter,r*http.Request){deferfunc(){iferr:=recover();err!=nil{log.Printf("panic: %v",err)http.Error(w,"Internal Server Error",500)}}()next.ServeHTTP(w,r)})}// 2. goroutine 中的 panic 恢复funcsafeGoroutine(){deferfunc(){ifr:=recover();r!=nil{log.Printf("goroutine panic: %v",r)}}()// goroutine 中的代码riskyOperation()}

7.5 错误处理模式

错误聚合

// 收集多个错误typeMultiErrorstruct{Errors[]error}func(me*MultiError)Error()string{varmsgs[]stringfor_,err:=rangeme.Errors{msgs=append(msgs,err.Error())}returnstrings.Join(msgs,"; ")}func(me*MultiError)Add(errerror){iferr!=nil{me.Errors=append(me.Errors,err)}}func(me*MultiError)HasErrors()bool{returnlen(me.Errors)>0}// 使用funcvalidateUser(user User)error{me:=&MultiError{}ifuser.Name==""{me.Add(errors.New("name is required"))}ifuser.Age<0{me.Add(errors.New("age must be positive"))}ifuser.Email==""{me.Add(errors.New("email is required"))}ifme.HasErrors(){returnme}returnnil}

错误重试

// 带重试的函数执行funcretry(attemptsint,sleep time.Duration,fnfunc()error)error{iferr:=fn();err!=nil{ifattempts--;attempts>0{time.Sleep(sleep)returnretry(attempts,sleep,fn)}returnerr}returnnil}// 使用err:=retry(3,time.Second,func()error{returndoSomething()})

7.6 练习

  1. 实现一个自定义的 HTTP 错误类型
  2. 编写一个函数,验证用户输入并返回所有验证错误
  3. 实现一个带重试机制的网络请求函数
  4. 编写一个 panic 恢复中间件

7.7 下一章

下一章我们将学习 Go 语言的并发编程。