# CodeReview
## 研发军规更新
1. 【强制】Handler层的接口需要明确使用请求和响应结构体作为出入参,尽量避免使用匿名结构和Map结构,不允许使用interface{}类型作为出参
```
func (u *User) Login(req *LoginReq, r *http.Request) *LoginRsp {
// logic code segment
}
```
2. 【强制】每个导出方法应添加注释,注释使用统一格式
工具:VsCode(**koroFileHeader**),Goland(***Goanno***)
示例:
```
// GetFuncByCloudRoom
//
// @Description: 获取cloudroomFunc
//
// @Author: author
//
// @Date: 2022-02-14 14:59:30
//
// @Receiver:
//
// @Return:
//
// @param {*billdifftype.BillReportReq} req
```
3. 【强制】接口请求和响应结构体应在每个字段上添加描述,描述字段作用及含义
```
type LoginReq struct {
Username string `json:"username" desc:"用户名" v:"required#请输入用户名称"`
Password string `json:"password" desc:"用户密码" v:"required#请输入用户密码"`
}
```
4. 【强制】handler的出参入参名称和接口名字对应,rdb层参数和表结构名称对应
```json
func (u *User) Login(req *LoginReq, r *http.Request) *LoginRsp {
// logic code segment
}
```
5. 【强制】Type使用规范,每个服务应对应一个Type仓库,仓库中包含接口定义、业务结构和常量
```
service-s-sysmgmt-type
|_ enumtype
|_ enum_api.go // 接口入参返参定义
|_ enum_type.go // 数据库对象及业务对象
|_ enum_vars.go // 常量定义
|_ commontype
|_ common_type.go // 服务通用对象
|_ common_vars.go // 服务通用常量定义
```
6. 【强制】内部程序接口响应时间应低于1秒,处理时间高于1秒的接口应考虑使用缓存、异步、预加载处理
7. 【强制】每个function长度应不超于300行
8. 【强制】每个function注释率应达到7%(注释行数/函数长度),仅计入有可读性的有效注释(内部变量、分支、核心逻辑、特殊处理的逻辑)
9. 【强制】所有代码修改提交必须有对应JIRA ID,git commit message 要写清楚本次变更的内容,版本
10. 【强制】代码提交前必须gofmt、go build
11. 【强制】禁止无退出条件的for循环,无退出条件的for循环可能导致时间片无法轮转,主进程被阻塞,程序无法正常运行
12. 【强制】使用下标访问切片时需判断数组越界
13. 【强制】go routine中的panic错误必须被defer块捕获
14. 【推荐】module业务模块开发中,尽量使用多参数返回error处理错误,而非使用panic上抛错误
15. 【强制】方法名、参数名、结构体都统一使用lowerCamelCase风格,必须遵从驼峰形式;
16. 【强制】常量不可在代码,或者SQL中写死,应采用定义常量的方式,常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长;
17. 【强制】方法参数不能超过5个,如果参数过长,应使用对象进行封装
18. 【强制】圈复杂度,一个方法中for循环、if判断等一共不能超过五层,超过则要拆分
19. 【强制】代码重复率,每个方法涉及遵循单一职责原则,不要为了实现一个逻辑写重复的代码,复用写过的代码
20. 【建议】测试用例覆盖,每个方法都要用对应的测试用例,测试用例覆盖率要达到60%
## 日志规范
1. 日志格式定义
```
【例】:
2021-10-11 21:00:01.230 [DEBUG][Service-b-kubernetes][RequestId: 1722kdkd2skckcsssccddlsss] webservice.go:122: request data : map[page:1 product:tdach size:10]
```
2. 定义日志
2.1 日志配置
```
[logger]
path = "/var/log/" # 日志文件路径。默认为空,表示关闭,仅输出到终端
file = "{Y-m-d}.log" # 日志文件格式。默认为"{Y-m-d}.log"
level = "all" # 日志输出级别
ctxKeys = [] # Context上下文变量名称,自动打印Context的变量到日志中。默认为空
stdoutPrint = true # 日志是否同时输出到终端。默认true
rotateSize = 0 # 按照日志文件大小对文件进行滚动切分。默认为0,表示关闭滚动切分特性
rotateExpire = 0 # 按照日志文件时间间隔对文件滚动切分。默认为0,表示关闭滚动切分特性
rotateBackupLimit = 0 # 按照切分的文件数量清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除
rotateBackupExpire = 0 # 按照切分的文件有效期清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除
rotateBackupCompress = 0 # 滚动切分文件的压缩比(0-9)。默认为0,表示不压缩
rotateCheckInterval = "1h" # 滚动切分的时间检测间隔,一般不需要设置。默认为1小时
```
2.2 错误类型的定义
一个错误码通常包含三个部分:
- System/App short name: 系统或者应用的名称,如 RST, OSS等。如果你熟悉Jira的话,基本也是这个规范。
- Component short name or code: 系统内部的组件名称或者编码,如LOGIN, AUDIT,001 这些都可以,方便更快地定位错误。
- Status code: 错误的状态码,这个是一个三位数字的状态码,如200,404,500,主要是借鉴自 HTTP Status Code,毕竟绝大多数开发者都了解HTTP状态码,我们没有必要再重新设计。
有了上述的规范后,让我们看一下典型的错误编码长什么样子:
- OSS-001-404: 你应该知道是OSS的某一组件报告资源没有找到吧
- RST-002-500:这个是一个组件的内部错误
- UIC-LOGIN-404:这个应该是会员登录时查找不到指定的账号
2.3 model 入参 引入pcontext (可扩展的context入参)
```
func getEnum(code string, context *pcontext.CommonInfo) ([]*enumtype.Enum, error) {
rsp, err := prpc.NewHTTPClient().NewRequest("service-b-sysmgmt", "/service-b-sysmgmt/v1/enum/get").Header(map[string][]string{
"Authorization": {context.Token},
}).Params(
map[string][]string{
"enum_key": {code},
}).GET()
if err != nil {
return nil, err
}
b, err := ioutil.ReadAll(rsp.Body)
if err != nil {
return nil, err
}
enums := &struct {
Data struct {
Enums []*enumtype.Enum `json:"enums"`
} `json:"data"`
}{}
err = json.Unmarshal(b, enums)
if err != nil {
return nil, err
}
return enums.Data.Enums, nil
}
```
2.4 LEGO wrapper结构体反参示例:
```
{
"code": 0,
"data": null,
"req_id": "abccccsdfasadf", //TRACEID
"biz_code": "OSS-001-404", //业务错误码
"error": "错误信息:.......ErrorMsg" //业务错误信息
}
```
2. 日志分级()
**error** - 指比较严重的错误,对正常业务有影响,需要运维配置监控的;
示例
```
2022-03-14 12:59:00.098 [ERROR] [Service-b-kubernetes] [RequestIdId: 1722kdkd2skckcsssccddlsss][DEFAULT_HANDLER_WRAPPER] EOF
service-b-kubernetes_1 | Stack:
service-b-kubernetes_1 | 1. msp-git.connext.com.cn/connext-go-third/third-log/plog.Error
service-b-kubernetes_1 | /root/go/pkg/mod/msp-git.connext.com.cn/connext-go-third/third-log@v0.0.0-20210105051823-25284ece4149/plog/logger.go:32
service-b-kubernetes_1 | 2. msp-git.connext.com.cn/connext-go-core/core-util/prouter.(*routerBuilder).buildHandler.func1.1
service-b-kubernetes_1 | /root/go/pkg/mod/msp-git.connext.com.cn/connext-go-core/core-util@v0.0.0-20220304034920-244d421c9669/prouter/router_builder.go:125
service-b-kubernetes_1 | 3. msp-git.connext.com.cn/connext-go-third/third-log/plog.Assert
service-b-kubernetes_1 | /root/go/pkg/mod/msp-git.connext.com.cn/connext-go-third/third-log@v0.0.0-20210105051823-25284ece4149/plog/logger.go:38
service-b-kubernetes_1 | 4. msp-git.connext.com.cn/connext-go-service/service-b-kubernetes/handler/kubernetesservicehandler.(*KubernetesService).Create
service-b-kubernetes_1 | /var/jenkins_home/workspace/golang_ci@2/handler/kubernetesservicehandler/kubernetes_service.go:76
service-b-kubernetes_1 | 5. msp-git.connext.com.cn/connext-go-core/core-util/prouter.(*routerBuilder).buildHandler.func1.2
service-b-kubernetes_1 | /root/go/pkg/mod/msp-git.connext.com.cn/connext-go-core/core-util@v0.0.0-20220304034920-244d421c9669/prouter/router_builder.go:156
service-b-kubernetes_1 | 6. msp-git.connext.com.cn/connext-go-core/core-util/prouter.glob..func7.1
service-b-kubernetes_1 | /root/go/pkg/mod/msp-git.connext.com.cn/connext-go-core/core-util@v0.0.0-20220304034920-244d421c9669/prouter/router_wrapper_call.go:57
service-b-kubernetes_1 | 7. msp-git.connext.com.cn/connext-go-core/core-util/prouter.glob..func6.1
service-b-kubernetes_1 | /root/go/pkg/mod/msp-git.connext.com.cn/connext-go-core/core-util@v0.0.0-20220304034920-244d421c9669/prouter/router_wrapper_call.go:47
service-b-kubernetes_1 | 8. msp-git.connext.com.cn/connext-go-core/core-util/prouter.(*routerBuilder).buildHandler.func1
service-b-kubernetes_1 | /root/go/pkg/mod/msp-git.connext.com.cn/connext-go-core/core-util@v0.0.0-20220304034920-244d421c9669/prouter/router_builder.go:163
service-b-kubernetes_1 | 9. msp-git.connext.com.cn/connext-go-core/core-util/prouter.glob..func15.1
service-b-kubernetes_1 | /root/go/pkg/mod/msp-git.connext.com.cn/connext-go-core/core-util@v0.0.0-20220304034920-244d421c9669/prouter/router_wrapper_handler.go:101
service-b-kubernetes_1 | 10. msp-git.connext.com.cn/connext-go-core/core-util/prouter.glob..func16.1
service-b-kubernetes_1 | /root/go/pkg/mod/msp-git.connext.com.cn/connext-go-core/core-util@v0.0.0-20220304034920-244d421c9669/prouter/router_wrapper_handler.go:111
service-b-kubernetes_1 | 11. msp-git.connext.com.cn/connext-go-core/core-util/prouter.glob..func17.1
service-b-kubernetes_1 | /root/go/pkg/mod/msp-git.connext.com.cn/connext-go-core/core-util@v0.0.0-20220304034920-244d421c9669/prouter/router_wrapper_handler.go:123
service-b-kubernetes_1 | 12. msp-git.connext.com.cn/connext-go-core/core-util/prouter.glob..func11.1
service-b-kubernetes_1 | /root/go/pkg/mod/msp-git.connext.com.cn/connext-go-core/core-util@v0.0.0-20220304034920-244d421c9669/prouter/router_wrapper_handler.go:43
```
**warn** - 警告信息,一般的错误,对业务影响不大,但是需要开发关注;
```
2022-03-14 03:47:06 [WARN][Service-b-kubernetes][RequestIdId: 1722kdkd2skckcsssccddlsss] webservice.go:122: request data : map[page:1 product:tdach size:10]
```
**info** - 信息日志,记录排查问题的关键信息,如调用时间、出参入参等等;
```
2022-03-14 03:47:06 [INFO] [Service-b-kubernetes][RequestIdId: 1722kdkd2skckcsssccddlsss] queryproject.go:44 [len(projectitem.NetworkExternalList) == 0,return]
```
**debug** - 调试信息,可记录详细的业务处理到哪一步了,以及当前的变量状态,用于开发DEBUG的,关键逻辑里面的运行时数据;
```
2022-03-14 03:47:06 [DEBUG] [Service-b-kubernetes][RequestIdId: 1722kdkd2skckcsssccddlsss] queryproject.go:44 [len(projectitem.NetworkExternalList) == 0,return]
```
**gf** - 参考日志级别如下
```
LEVEL_ALL
LEVEL_DEV
LEVEL_PROD
LEVEL_DEBU
LEVEL_INFO
LEVEL_NOTI
LEVEL_WARN
LEVEL_ERRO
```
3. 有意义的日志
程序启动,退出的时间点;
程序运行消耗时间;
耗时程序的执行进度,不然程序开始运行后半天没一点输出挺让人着急啊~
重要变量的状态变化。
```
```
4. 日志安全
涉及隐私信息需要加密处理
5. Golang异常处理规则
1.【强制】永远不要吞掉异常,否则在系统发生错误时,你永远不知道到底发生了什么
2.【强制】尽量使用特定的异常
3.【强制】正确的封装和传递异常,不要丢失异常栈,因为异常栈对于定位原始错误很关键
4.【强制】不要在defer块中抛出异常
5.【强制】禁止在线上环境开启 debug
禁止在线上环境开启debug,这一点非常重要。
因为一般系统的debug日志会很多,并且各种框架中也大量使用debug的日志,线上开启debug不久可能会打满磁盘,影响业务系统的正常运行。
6.【强制】避免重复打印日志
```
【反例】:
if(user.isVip()){
plog.info("该用户是会员,Id:{}",user,getUserId());
//冗余,可以跟前面的日志合并一起
plog.info("开始处理会员逻辑,id:{}",user,getUserId());
//会员逻辑
}else{
//非会员逻辑
}
```
7.【建议】日志文件分离
8.【强制】核心功能模块,打印较完整的日志
9.【建议】不要使用异常来控制程序逻辑流程
10.【建议】及早校验用户的输入
11.【建议】在打印错误的log中尽量在一行中包含尽可能多的上下文
12.【建议】遇到if...else...等条件时,每个分支首行都尽量打印日志.
```
【正例】:
if user.isVip() {
plog.info("该用户是会员,Id:%+v,开始处理会员逻辑",user,getUserId());
//会员逻辑
} else {
plog.info("该用户是非会员,Id:%+v,开始处理非会员逻辑",user,getUserId())
//非会员逻辑
}
```
13.【建议】自定义日志使用参数占位%+v,而不是用+拼接。
```
【正例】:
plog.info("Processing trade with id: %+v and symbol : %+v ", id, symbol);
```
```
【反例】:
plog.info("Processing trade with id: " + id + " and symbol: " + symbol);
```
我们使用了%+v来作为日志中的占位符,比使用+操作符,更加优雅简洁。并且,相对于反例,使用占位符仅是替换动作,可以有效提升性能。
14.【建议】使用异步的方式来输出日志。
```
plog.info("Processing trade with id: %+v and symbol : %+v ", id, symbol);
```
日志最终会输出到文件或者其它输出流中,IO性能会有要求。如果异步,就可以显著提升IO性能。