go 代码质量提高总结
- go的结构体和java的对象不同,不用单独建个models包单独存放,可以和data读取层一起定义。(此处排除使用go orm等框架自动生成代码的情况),最好在用的那个包定义结构体,并写好结构体的注释,包括结构体自身的描述和其中属性的描述。
- 结构体字段最好保持对齐,注释写在字段后边,也保持对齐,例如:
// 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"` // 名称
}
- 和其它语言不一样,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
- 对于replace的写法,应该只在开发阶段使用,而不能提交到git上。或者使用go1.18的go.work替代。需要保证对于新加入的成员,可以快速轻松的启动项目,而不是有很多弯弯绕绕的东西要配置。
- 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()
}
- 可以使用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
- grpc客户端不用保持连接池。go 的grpc是基于http2.0的。自带了IO多路复用,如果单连接性能不行,那么多连接也不会有什么太大的改善。全局保持一个连接即可。
- go的封装和java不一样,小写字母开头代表模块内私有,大写字母开头代码对外部可用。包内私有的变量和方法应该使用小写字母开头,不应该暴露出去,以防止潜在的安全问题,也使得项目对外的接口更加清晰易于理解。
- 注释不应该过多的用来解释代码做了什么,而是应该描述为什么这么做。好的代码应该基于其变量和函数命名就可以读懂。
- 推荐使用命名的方式返回参数,这样会提高代码的可读性。并且更好区分同类型的返回值的含义。
- 导入依赖应该按照(1)基本库、(2)第三方库、(3)公司通用库、(4)项目内库 的方式区分开,方便阅读和管理。
- 日志应该易于定位问题出错的源头,并且能描述清楚发生的问题,最好不需要查看代码就能定位错误的原因。拒绝打印无意义的流程日志(不写或者降低其日志级别)。
- 对于函数内发生的会导致失败的错误,不应该将其隐藏,而应该显示的将其返回。更不应该使用panic的方式抛出(尤其是业务代码)。
- 对于协程,一定要传入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)
}
}
}()
}
版权声明
本文章由作者“衡于墨”创作,转载请注明出处,未经允许禁止用于商业用途
发布时间:2022年05月17日 13:36:49
备案号:
闽ICP备19015193号-1
关闭特效
评论区#
还没有评论哦,期待您的评论!
引用发言