美文网首页
go接口代码简洁开发

go接口代码简洁开发

作者: 彳亍口巴 | 来源:发表于2022-02-09 17:32 被阅读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.donewg.add(),也不用每次去定义错误了

相关文章

网友评论

      本文标题:go接口代码简洁开发

      本文链接:https://www.haomeiwen.com/subject/jzowkrtx.html