search.png
关于我
menu.png
go 代码质量提高总结
  1. go的结构体和java的对象不同,不用单独建个models包单独存放,可以和data读取层一起定义。(此处排除使用go orm等框架自动生成代码的情况),最好在用的那个包定义结构体,并写好结构体的注释,包括结构体自身的描述和其中属性的描述。
  2. 结构体字段最好保持对齐,注释写在字段后边,也保持对齐,例如:
// The request message containing the user's name.
type HelloRequest struct {
    state         protoimpl.MessageState   // state
    sizeCache     protoimpl.SizeCache      // sizeCache
    unknownFields protoimpl.UnknownFields  // unknownFields 

    Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`  // 名称
}
  1. 和其它语言不一样,go是以模块为单位导入的,而不是以文件,所以以下的目录是不提倡的:
-- utils
   -- test
     -- img_utils_test.go
     -- encrypt_utils_test.go
   -- img_utils.go
   -- encrypt_utils.go

并且单元测试不用单独建立包来存放,合适的目录结构应该是:

-- utils
   -- img_test.go
   -- encrypt_test.go
   -- img.go
   -- encrypt.go
  1. 对于replace的写法,应该只在开发阶段使用,而不能提交到git上。或者使用go1.18的go.work替代。需要保证对于新加入的成员,可以快速轻松的启动项目,而不是有很多弯弯绕绕的东西要配置。
  2. grpc的返回结果不应该携带code, msg。应该将其封装到异常中。力保返回正确时,err为nil。返回存在错误时,err 有错误原因,客户端可以通过方法判断异常的类型。例如kratos框架根据pb生成的错误代码就很方便:
// error_reason.proto文件
enum ErrorReason {
  GEETER_UNSPECIFIED = 0;
  USER_NOT_FOUND = 1;
}

// error_reason.pb.go
type ErrorReason int32

const (
    ErrorReason_GEETER_UNSPECIFIED ErrorReason = 0
    ErrorReason_USER_NOT_FOUND     ErrorReason = 1
)

// error_reason_errors.pb.go
func ErrorUserNotFound(format string, args ...interface{}) *errors.Error {
    return errors.New(404, ErrorReason_USER_NOT_FOUND.String(), fmt.Sprintf(format, args...))
}

func IsUserNotFound(err error) bool {
    if err == nil {
        return false
    }
    e := errors.FromError(err)
    return e.Reason == ErrorReason_USER_NOT_FOUND.String() 
}
  1. 可以使用kratos的校验机制来对参数进行校验,保证接口的可靠和正确性,也减少了代码量:
message SomeRequest {
  string name = 1[(validate.rules).string = {min_len: 0, max_len: 32}];    // 用户名称 
}

http

httpSrv := http.NewServer(
    http.Address(":8000"),
    http.Middleware(
        validate.Validator(),
    ))

grpc

grpcSrv := grpc.NewServer(
    grpc.Address(":9000"),
    grpc.Middleware(
        validate.Validator(),
    ))

具体可以参考https://go-kratos.dev/docs/component/middleware/validate

  1. grpc客户端不用保持连接池。go 的grpc是基于http2.0的。自带了IO多路复用,如果单连接性能不行,那么多连接也不会有什么太大的改善。全局保持一个连接即可。
  2. go的封装和java不一样,小写字母开头代表模块内私有,大写字母开头代码对外部可用。包内私有的变量和方法应该使用小写字母开头,不应该暴露出去,以防止潜在的安全问题,也使得项目对外的接口更加清晰易于理解。
  3. 注释不应该过多的用来解释代码做了什么,而是应该描述为什么这么做。好的代码应该基于其变量和函数命名就可以读懂。
  4. 推荐使用命名的方式返回参数,这样会提高代码的可读性。并且更好区分同类型的返回值的含义。
  5. 导入依赖应该按照(1)基本库、(2)第三方库、(3)公司通用库、(4)项目内库 的方式区分开,方便阅读和管理。
  6. 日志应该易于定位问题出错的源头,并且能描述清楚发生的问题,最好不需要查看代码就能定位错误的原因。拒绝打印无意义的流程日志(不写或者降低其日志级别)。
  7. 对于函数内发生的会导致失败的错误,不应该将其隐藏,而应该显示的将其返回。更不应该使用panic的方式抛出(尤其是业务代码)。
  8. 对于协程,一定要传入context上下文,或者用于通知结束的channel,用来保证其可以正确退出。以防止携程泄露,例如:
func tickFun(exit <-chan struct{}) {
    tick := time.NewTicker(1 * time.Second)
    defer tick.Stop()
    for {
        select {
        case <-tick.C:
            fmt.Println("tick")
        case <-exit:
            return
        }
    }
}

或者

func watch(ctx context.Context, msg chan string) {
    go func() {
    exit:
        for {
            select {
            case <-ctx.Done():
                break exit
            case msg <- msgChan:
                fmt.Println(msg)
            }
        }
    }()
}

版权声明

知识共享许可协议 本文章由作者“衡于墨”创作,转载请注明出处,未经允许禁止用于商业用途

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
发布时间:2022年05月17日 13:36:49

评论区#

还没有评论哦,期待您的评论!

关闭特效