赞
踩
最近在学习go语言,以此记录日常编码中的最佳实践,欢迎大家一起讨论
使用Goanno插件:https://github.com/loveinsky100/goanno
以goland为例
2. 编辑模板
- // ${function_name} ${todo}
- // @receiver ${receiver}
- // @param ${params}
- // @return ${return_types}
- // GetNode 获取指定结构,从缓存加载
- //
- // @receiver c
- // @param ctx
- // @param countryCode 国家码
- // @return *Node
- func (c *Cache) GetNode(ctx context.Context, countryCode string) *Node, error {
- ...
- return cacheNode, nil
- }
建议使用zap的零内存分配api,性能比go标准库的api好:GitHub - uber-go/zap: Blazing fast, structured, leveled logging in Go.
go最受争议的部分之一就是错误处理,这里不去讨论其好坏,仍然推荐大家使用官方的处理方式:返回error接口,不建议使用panic + recover,容易导致程序崩溃
- func f() error {
- if ... {
- return errors.New("xxx")
- }
- return nil
- }
部分goer建议使用panic + recover就是因为其有堆栈而error没有,但我们可以实现新的error接口时增加堆栈记录
- type BizError struct {
- code string
- message string
- cause error
-
- stacktrace struct {
- onCreate *stacktrace
- onPanic *stacktrace
- }
- }
-
- // NewBizError create a new BizError
- // - cause can be nil is no underlying error
- // - omitStacks indicates how many frames should be dropped, if <=0 no stacks will be filled
- func NewBizError(code api.ResultCode, message string, cause error, omitStacks int, throwInPlace bool) *BizError {
- err := &BizError{
- code: code.Code(),
- message: message,
- cause: cause,
- }
- if omitStacks >= 0 {
- err.stacktrace.onCreate = dumpStacktrace(omitStacks + 1)
- if throwInPlace {
- err.stacktrace.onPanic = err.stacktrace.onCreate
- }
- }
- return err
- }
-
- func (this *BizError) Error() string {
- if len(this.message) > 0 {
- return this.code + ": " + this.message
- } else {
- return this.code + ": [NO_MESSAGE]"
- }
- }
-
- type stacktrace struct {
- header []byte // goroutine header (e.g., "goroutine 3 [running]:")
- frames []byte // the stacktrace details
- }
-
- func dumpStacktrace(skip int) *stacktrace {
- skip += 2 // skip debug.Stack and this frame
- skip *= 2 // 2-line each frame
- stack := debug.Stack()
- var header []byte
- for i, b := range stack { // assumes no unicode in stack, iterate on bytes
- if b == '\n' {
- if header == nil {
- // consume first line as goroutine header
- header = stack[:i]
- } else {
- skip--
- if skip == 0 {
- stack = stack[i:]
- break
- }
- }
- }
- }
- if skip > 0 {
- panic("skip overflow")
- }
- return &stacktrace{header, stack}
- }

这样使用时即可记录堆栈
因为 Golang 语言是强类型,所以经常会使用到类型转换,所以在这里推荐类型转换三方库:GitHub - spf13/cast: safe and easy casting from one type to another in Go
- cast.ToString("mayonegg") // "mayonegg"
- cast.ToString(8) // "8"
- cast.ToString(8.31) // "8.31"
- cast.ToString([]byte("one time")) // "one time"
- cast.ToString(nil) // ""
-
- var foo interface{} = "one more time"
- cast.ToString(foo) // "one more time"
-
- cast.ToInt(8) // 8
- cast.ToInt(8.31) // 8
- cast.ToInt("8") // 8
- cast.ToInt(true) // 1
- cast.ToInt(false) // 0
-
- var eight interface{} = 8
- cast.ToInt(eight) // 8
- cast.ToInt(nil) // 0

golang原生对json已经做了很好的支持,简单易用,但其性能一直为人垢病,因此建议使用字节开源的工具sonic。除了能平替原生的json使用姿势外,在性能上面也是从底层方面做了很多文章进行优化,性能方面遥遥领先:sonic:基于 JIT 技术的开源全场景高性能 JSON 库_原生云_火山引擎开发者社区_InfoQ写作社区
要知道go的超时时间不像java那样每个调用都固定超时时间,而是以总体时间来计算,但有时部分下游就是需要超出原超时时间进行(当然必须是异步调用,否则就自相矛盾了),具体代码如下:
- // 注意:必须先cancel再设置,否则只能设置比原来时间更短的时间
- func TestTimeout(t *testing.T) {
- ctx := context.Background()
- ctx, _ = context.WithTimeout(ctx, time.Second*3)
- ctx, _ = context.WithTimeout(ctx, time.Second*6)
-
- deadline, _ := ctx.Deadline()
- fmt.Println(deadline.Sub(time.Now())) // 2.99s, 直接覆盖设置不生效
-
- ctx, _ = context.WithTimeout(ctx, time.Second*1)
- deadline, _ = ctx.Deadline()
- fmt.Println(deadline.Sub(time.Now())) // 0.99s 设置更短时间, 生效
-
- ctx = context.WithoutCancel(ctx) // 取消
- ctx, _ = context.WithTimeout(ctx, time.Second*6) // 重新设置
- deadline, _ = ctx.Deadline()
- fmt.Println(deadline.Sub(time.Now())) // 5.99s, 取消后再设置, 才生效
- }

这样会再生成一个proto的validate文件
建议consumer在远程调用前先调用validate方法,当参数不合法时提前感知
- func remote(ctx context.Context) {
- request := ...
- if err := request.ValidateAll(); err != nil {
- return nil, errors.New(err.Error())
- }
- remoteClient.GetXXX(ctx, request)
- }
provider在实现时也必须调用validate,防止参数不合法
- func (s *server) Remote(ctx context.Context, request XXX) Response, error {
- if err := request.ValidateAll(); err != nil {
- return nil, errors.New(err.Error())
- }
- // 处理逻辑
- }
但每个方法都要加validate重复代码,有没有办法统一处理参数校验呢,当然是有的
- type validator interface {
- ValidateAll() error
- }
-
- // ValidateAllInterceptor
- //
- // @Description: grpc服务注册的参数校验拦截器
- // @return grpc.UnaryServerInterceptor
- func ValidateAllInterceptor() grpc.UnaryServerInterceptor {
- return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (reply interface{}, err error) {
- if v, ok := req.(validator); ok {
- if err := v.ValidateAll(); err != nil {
- return nil, err
- }
- }
- return handler(ctx, req)
- }
- }

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。