0


golang编码最佳实践(持续更新中)

最近在学习go语言,以此记录日常编码中的最佳实践,欢迎大家一起讨论

注释模板

使用Goanno插件:https://github.com/loveinsky100/goanno

设置模板

以goland为例

  1. 选择“工具-Goanno设置”

  1. 编辑模板
  1. // ${function_name} ${todo}
  2. // @receiver ${receiver}
  3. // @param ${params}
  4. // @return ${return_types}

案例

  1. // GetNode 获取指定结构,从缓存加载
  2. //
  3. // @receiver c
  4. // @param ctx
  5. // @param countryCode 国家码
  6. // @return *Node
  7. func (c *Cache) GetNode(ctx context.Context, countryCode string) *Node, error {
  8. ...
  9. return cacheNode, nil
  10. }

日志打印

建议使用zap的零内存分配api,性能比go标准库的api好:GitHub - uber-go/zap: Blazing fast, structured, leveled logging in Go.

格式处理

  • %v: 根据值的类型自动选择合适的格式输出。
  • %+v: 类似 %v,但会输出更多的信息,如字段名称。
  • %#v: 输出值的完整 Go 语法表示,包括类型信息。
  • 不要直接打印byte[]类型等无实际意义的日志,应该转成string打印。

错误处理

go最受争议的部分之一就是错误处理,这里不去讨论其好坏,仍然推荐大家使用官方的处理方式:返回error接口,不建议使用panic + recover,容易导致程序崩溃

  1. func f() error {
  2. if ... {
  3. return errors.New("xxx")
  4. }
  5. return nil
  6. }

解决error无堆栈

部分goer建议使用panic + recover就是因为其有堆栈而error没有,但我们可以实现新的error接口时增加堆栈记录

  1. type BizError struct {
  2. code string
  3. message string
  4. cause error
  5. stacktrace struct {
  6. onCreate *stacktrace
  7. onPanic *stacktrace
  8. }
  9. }
  10. // NewBizError create a new BizError
  11. // - cause can be nil is no underlying error
  12. // - omitStacks indicates how many frames should be dropped, if <=0 no stacks will be filled
  13. func NewBizError(code api.ResultCode, message string, cause error, omitStacks int, throwInPlace bool) *BizError {
  14. err := &BizError{
  15. code: code.Code(),
  16. message: message,
  17. cause: cause,
  18. }
  19. if omitStacks >= 0 {
  20. err.stacktrace.onCreate = dumpStacktrace(omitStacks + 1)
  21. if throwInPlace {
  22. err.stacktrace.onPanic = err.stacktrace.onCreate
  23. }
  24. }
  25. return err
  26. }
  27. func (this *BizError) Error() string {
  28. if len(this.message) > 0 {
  29. return this.code + ": " + this.message
  30. } else {
  31. return this.code + ": [NO_MESSAGE]"
  32. }
  33. }
  34. type stacktrace struct {
  35. header []byte // goroutine header (e.g., "goroutine 3 [running]:")
  36. frames []byte // the stacktrace details
  37. }
  38. func dumpStacktrace(skip int) *stacktrace {
  39. skip += 2 // skip debug.Stack and this frame
  40. skip *= 2 // 2-line each frame
  41. stack := debug.Stack()
  42. var header []byte
  43. for i, b := range stack { // assumes no unicode in stack, iterate on bytes
  44. if b == '\n' {
  45. if header == nil {
  46. // consume first line as goroutine header
  47. header = stack[:i]
  48. } else {
  49. skip--
  50. if skip == 0 {
  51. stack = stack[i:]
  52. break
  53. }
  54. }
  55. }
  56. }
  57. if skip > 0 {
  58. panic("skip overflow")
  59. }
  60. return &stacktrace{header, stack}
  61. }

这样使用时即可记录堆栈

类型转换

因为 Golang 语言是强类型,所以经常会使用到类型转换,所以在这里推荐类型转换三方库:GitHub - spf13/cast: safe and easy casting from one type to another in Go

  1. cast.ToString("mayonegg") // "mayonegg"
  2. cast.ToString(8) // "8"
  3. cast.ToString(8.31) // "8.31"
  4. cast.ToString([]byte("one time")) // "one time"
  5. cast.ToString(nil) // ""
  6. var foo interface{} = "one more time"
  7. cast.ToString(foo) // "one more time"
  8. cast.ToInt(8) // 8
  9. cast.ToInt(8.31) // 8
  10. cast.ToInt("8") // 8
  11. cast.ToInt(true) // 1
  12. cast.ToInt(false) // 0
  13. var eight interface{} = 8
  14. cast.ToInt(eight) // 8
  15. cast.ToInt(nil) // 0

json工具

golang原生对json已经做了很好的支持,简单易用,但其性能一直为人垢病,因此建议使用字节开源的工具sonic。除了能平替原生的json使用姿势外,在性能上面也是从底层方面做了很多文章进行优化,性能方面遥遥领先:sonic:基于 JIT 技术的开源全场景高性能 JSON 库_原生云_火山引擎开发者社区_InfoQ写作社区

web/rpc服务

单独为某个下游接口设置超时时间

要知道go的超时时间不像java那样每个调用都固定超时时间,而是以总体时间来计算,但有时部分下游就是需要超出原超时时间进行(当然必须是异步调用,否则就自相矛盾了),具体代码如下:

  1. // 注意:必须先cancel再设置,否则只能设置比原来时间更短的时间
  2. func TestTimeout(t *testing.T) {
  3. ctx := context.Background()
  4. ctx, _ = context.WithTimeout(ctx, time.Second*3)
  5. ctx, _ = context.WithTimeout(ctx, time.Second*6)
  6. deadline, _ := ctx.Deadline()
  7. fmt.Println(deadline.Sub(time.Now())) // 2.99s, 直接覆盖设置不生效
  8. ctx, _ = context.WithTimeout(ctx, time.Second*1)
  9. deadline, _ = ctx.Deadline()
  10. fmt.Println(deadline.Sub(time.Now())) // 0.99s 设置更短时间, 生效
  11. ctx = context.WithoutCancel(ctx) // 取消
  12. ctx, _ = context.WithTimeout(ctx, time.Second*6) // 重新设置
  13. deadline, _ = ctx.Deadline()
  14. fmt.Println(deadline.Sub(time.Now())) // 5.99s, 取消后再设置, 才生效
  15. }

proto参数校验

建议使用PGV:GitHub - bufbuild/protoc-gen-validate: Protocol Buffer Validation - Being replaced by github.com/bufbuild/protovalidate

这样会再生成一个proto的validate文件

建议consumer在远程调用前先调用validate方法,当参数不合法时提前感知

  1. func remote(ctx context.Context) {
  2. request := ...
  3. if err := request.ValidateAll(); err != nil {
  4. return nil, errors.New(err.Error())
  5. }
  6. remoteClient.GetXXX(ctx, request)
  7. }

provider在实现时也必须调用validate,防止参数不合法

  1. func (s *server) Remote(ctx context.Context, request XXX) Response, error {
  2. if err := request.ValidateAll(); err != nil {
  3. return nil, errors.New(err.Error())
  4. }
  5. // 处理逻辑
  6. }

但每个方法都要加validate重复代码,有没有办法统一处理参数校验呢,当然是有的

  1. type validator interface {
  2. ValidateAll() error
  3. }
  4. // ValidateAllInterceptor
  5. //
  6. // @Description: grpc服务注册的参数校验拦截器
  7. // @return grpc.UnaryServerInterceptor
  8. func ValidateAllInterceptor() grpc.UnaryServerInterceptor {
  9. return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (reply interface{}, err error) {
  10. if v, ok := req.(validator); ok {
  11. if err := v.ValidateAll(); err != nil {
  12. return nil, err
  13. }
  14. }
  15. return handler(ctx, req)
  16. }
  17. }

本文转载自: https://blog.csdn.net/sunny8zhou/article/details/140842231
版权归原作者 sunny8zhou 所有, 如有侵权,请联系我们删除。

“golang编码最佳实践(持续更新中)”的评论:

还没有评论