Golang CodeReview

# 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性能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值