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){ifb0{return0,errors.New(除数不能为零)}returna/b,nil}// 使用result,err:divide(10,0)iferr!nil{fmt.Println(错误:,err)return}fmt.Println(结果:,result)7.2 自定义错误使用 errors.NewfuncvalidateAge(ageint)error{ifage0||age150{returnerrors.New(年龄必须在 0-150 之间)}returnnil}自定义错误类型// 定义错误类型typeValidationErrorstruct{FieldstringMessagestring}func(e*ValidationError)Error()string{returnfmt.Sprintf(验证错误: 字段 %s, %s,e.Field,e.Message)}// 使用funcvalidateEmail(emailstring)error{ifemail{returnValidationError{Field:email,Message:不能为空,}}if!strings.Contains(email,){returnValidationError{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(ErrNotFounderrors.New(not found)ErrUnauthorizederrors.New(unauthorized)ErrForbiddenerrors.New(forbidden))// 使用funcfindUser(idint)(*User,error){user:db.FindByID(id)ifusernil{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 与 recoverpanic// panic 用于不可恢复的错误funcdivide(a,bint)int{ifb0{panic(除数不能为零)}returna/b}// 捕获 panicfuncsafeDivide(a,bint)(resultint,errerror){deferfunc(){ifr:recover();r!nil{errfmt.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{msgsappend(msgs,err.Error())}returnstrings.Join(msgs,; )}func(me*MultiError)Add(errerror){iferr!nil{me.Errorsappend(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.Age0{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--;attempts0{time.Sleep(sleep)returnretry(attempts,sleep,fn)}returnerr}returnnil}// 使用err:retry(3,time.Second,func()error{returndoSomething()})7.6 练习实现一个自定义的 HTTP 错误类型编写一个函数验证用户输入并返回所有验证错误实现一个带重试机制的网络请求函数编写一个 panic 恢复中间件7.7 下一章下一章我们将学习 Go 语言的并发编程。