go接口代码简洁开发
2022-02-09 本文已影响0人
彳亍口巴
1、背景
在常见的go接口开发过程中,让人吐槽最多的是代码中有太多的是if err!=nil
,导致代码变得臃肿以及可读性变差,在开启多个协程时,处理协程抛出的错误也可能让代码变得臃肿,下面总结一下我的个人经验和习惯。
2、接口需求
根据请求的uin,从redis中读取对应的字符串作为name返回,最简单的get操作
func GetUserNameByUin(req *model.Req, rsp *model.Rsp) (int32, error) {
job, err := NewGetUserNameJob(req, rsp)
if err != nil {
fmt.Printf("NewGetUserNameJob failed! err:%s\n", err.Error())
return err.RetResult()
}
err = job.FetchData()
if err != nil {
fmt.Printf("FetchData failed! err:%s\n", err.Error())
}
job.Compose()
return 0, nil
}
package main
import (
"fmt"
"strconv"
"smallzhoutest/code_format/model"
"github.com/juju/errors"
)
type GetUserNameJob struct {
req *model.Req
rsp *model.Rsp
redisData map[string]string
}
func NewGetUserNameJob(req *model.Req, rsp *model.Rsp) (*GetUserNameJob, *ErrInfo) {
if req.Uin < 10000 {
return nil, NewErrInfo(fmt.Errorf("invalid params"), 10000)
}
return &GetUserNameJob{
req: req,
rsp: rsp,
}, nil
}
// FetchData 获取所需的各种数据
func (obj *GetUserNameJob) FetchData() *ErrInfo {
var err error
r := NewRedisHandler()
obj.redisData, err = r.GetAllData()
if err != nil {
return NewErrInfo(errors.Annotate(err, "r.GetData failed"),
10000)
}
return nil
}
// Compose 对数据进行组装
func (obj *GetUserNameJob) Compose() {
uin := strconv.Itoa(int(obj.req.Uin))
for key, value := range obj.redisData {
if uin == key {
obj.rsp.Name = value
break
}
}
}
package main
import (
"fmt"
"github.com/garyburd/redigo/redis"
)
type RedisHandler struct {
}
func NewRedisHandler() *RedisHandler {
return &RedisHandler{}
}
var redisCli redis.Conn
func init() {
var err error
redisCli, err = redis.Dial("tcp", "127.0.0.1:6379", redis.DialPassword("123456"))
if err != nil {
fmt.Println("connect redis error :", err)
return
}
//defer redisCli.Close()
fmt.Println("链接成功")
}
func (r *RedisHandler) GetAllData() (map[string]string, error) {
arr := make([]interface{}, 0)
key := "key2"
resMap, err2 := redis.StringMap(redisCli.Do("HGETALL", key))
if err2 != nil {
return nil, err
}
fmt.Println(resMap)
//
return resMap, nil
}
err.go
package main
import "fmt"
// ErrInfo 错误
type ErrInfo struct {
err error // 错误信息
retCode int32 // 返回码
}
// NewErrInfo 创建错误
func NewErrInfo(err error, retCode int32) *ErrInfo {
return &ErrInfo{
err: err,
retCode: retCode,
}
}
// Error 返回错误信息
func (e *ErrInfo) Error() string {
return fmt.Sprintf("err:%v, retCode:%d", e.err, e.retCode)
}
// RetResult 同时返回错误和返回码
func (e *ErrInfo) RetResult() (int32, error) {
return e.retCode, e.err
}
在最底层出现错误的时候,我们并没有将错误打印出来,而是使用fmt.Errorf创建错误,并将错误返回到上一层,之后每一层发现错误后都使用errors.Annotate
对错误进行拼接,知道错误返回到接口那里才将错误进行打印
关于"github.com/juju/errors"
几个常见的方法:
// testAnnotate
func testAnnotate() {
err := errors.Errorf("first failed!") // 创建一个error吗,功能和fmt.Errorf类似
err = NewErrInfo(errors.Annotate(err, "second failed!"), 10002) // Annotate:将两个错误拼接
fmt.Println(errors.Details(err)) // Details:返回错误详情,若重写了Error方法,则按照重写代码输出
}
// ErrInfo 错误
type ErrInfo struct {
err error // 错误信息
retCode int32 // 返回码
}
// NewErrInfo 创建错误
func NewErrInfo(err error, retCode int32) *ErrInfo {
return &ErrInfo{
err: err,
retCode: retCode,
}
}
// Error 返回错误信息,重写了Error方法
func (e *ErrInfo) Error() string {
return fmt.Sprintf("err:%v, retCode:%d", e.err, e.retCode)
}
// RetResult 同时返回错误和返回码
func (e *ErrInfo) RetResult() (int32, error) {
return e.retCode, e.err
}
开发多个协程的错误处理
func getUserNameBatch(req *model.Req, rsp *model.Rsp) (int32, error) {
job, err := NewGetUserNameBatchJob(req, rsp)
if err != nil {
fmt.Printf("NewGetUserNameBatchJob failed! err:%s\n", err.Error())
return err.RetResult()
}
err = job.FetchData()
if err != nil {
fmt.Printf("FetchData failed! %s\n", err.Error())
return err.RetResult()
}
return 0, nil
}
package main
import (
"fmt"
"smallzhoutest/code_format/model"
"github.com/juju/errors"
"golang.org/x/sync/errgroup"
)
type GetUserNameBatchJob struct {
req *model.Req
rsp *model.Rsp
redisData map[string]string
}
func NewGetUserNameBatchJob(req *model.Req, rsp *model.Rsp) (*GetUserNameBatchJob, *ErrInfo) {
if req.Uin < 10000 {
return nil, NewErrInfo(fmt.Errorf("invalid params"), 10000)
}
return &GetUserNameBatchJob{
req: req,
rsp: rsp,
}, nil
}
// FetchData 获取所需的各种数据
func (obj *GetUserNameBatchJob) FetchData() *ErrInfo {
//var err error
//r := NewRedisHandler()
//obj.redisData, err = r.GetAllData()
//if err != nil {
// return NewErrInfo(errors.Annotate(err, "r.GetData failed"),
// 10000)
//}
g := &errgroup.Group{}
g.Go(func() error {
return nil
})
g.Go(func() error {
return fmt.Errorf("b err")
})
g.Go(func() error {
return fmt.Errorf("a err")
})
g.Go(func() error {
return fmt.Errorf("c err")
})
g.Go(func() error {
return fmt.Errorf("d err")
})
g.Go(func() error {
return nil
})
if err := g.Wait(); err != nil {
return NewErrInfo(errors.Annotate(err, "g.wait failed!"), 10001)
}
return nil
}
不止每次不用处理wg.done
和wg.add()
,也不用每次去定义错误了