像 Awesome-Go 一样提升企业 Go 项目代码质量

1. Awesome-Go 项目

参与过awesome-go项目的gopher都知道,每个MR都必须满足它以下的要求:

Make sure that you've checked the boxes below before you submit PR:

  • I have added my package in alphabetical order.
  • I have an appropriate description with correct grammar.
  • I know that this package was not listed before.
  • I have added godoc link to the repo and to my pull request.
  • I have added coverage service link to the repo and to my pull request.
  • I have added goreportcard link to the repo and to my pull request.
  • I have read Contribution guidelines, maintainers note and Quality standard.

总结出来,就是两项关键质量指标:

这两项指标都通过量化的数值对合并项目进行审查。只有满足了指标的阀值要求,你提及的MR才会被合进awesome-go的主分支。

以下是我的两个合并进主分支的项目:

代码审查 Code Review,程序员本能就是抵制的。我也不列外,但是很多事即使开始抵制,一旦尝试过,就再也停不下来。代码审查Code Review属此类。

通过参与awesome-go项目和实际的Code Review实践,可以得出:一个 Go 项目的优劣至少在编译期就可以作出初步判断,而判断标准就是以下两项:

  • 静态代码审查的通过率
  • 单元测试的代码覆盖率

2. 企业Go项目代码审查

同样在企业内部也遇到相同的问题,面对开发人员能力水平的参差不齐以及各种Freestyle的代码风格, 大大增加了企业 Go 项目的维护难度。指定一套标准规范,同时将其固化到 CI 过程,是非常必要的。

awesome-go项目中采用的是开源的代码审查项目与集成服务,但这些服务仅用于开源项目。要移植到企业内部,需要一些必要的准备工作。分别找出静态代码审查的通过率单元测试的代码覆盖率替代方案。

2.1 代码审查神器: golangci-lint

通过收集筛选,静态代码审查工具选择golangci-lint项目。该项目的使用者包括了众多国际大厂: google、facebook、 IBM 等等。其可用性已经被充分的验证过了。

2.1.1 审查分类

golangci-lint中,它内置了绝大部分Go项目需要使用的审查工具。以下就其内置工具做一下简单的分类。分类能够帮助我们快速定制出必要的审查工具,哪些是必要的,哪些是可选的一目了然。

该分类,还可以通过以下命令查询:

$: golangci-lint help linters
...
##内置Linters分类
Linters presets:
bugs: bodyclose, errcheck, gosec, govet, scopelint, staticcheck, typecheck
complexity: gocyclo, nakedret
format: gofmt, goimports
performance: bodyclose, maligned, prealloc
style: depguard, dupl, gochecknoglobals, gochecknoinits, goconst, gocritic, golint, gosimple, interfacer, lll, misspell, stylecheck, unconvert
unused: deadcode, ineffassign, structcheck, unparam, unused, varcheck

更详细的工具使用说明,可以直接跳转到对应的项目页进行查询。

代码BUG审查

  • bodyclose - checks whether HTTP response body is closed successfully
  • errcheck - Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases
  • gosec - Inspects source code for security problems
  • govet - Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
  • scopelint - Scopelint checks for unpinned variables in go programs
  • staticcheck - Staticcheck is a go vet on steroids, applying a ton of static analysis checks
  • typecheck - Like the front-end of a Go compiler, parses and type-checks Go code

代码复杂度

  • gocyclo - Computes and checks the cyclomatic complexity of functions
  • nakedret - Finds naked returns in functions greater than a specified function length

代码格式化

  • gofmt - Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification
  • goimports - Goimports does everything that gofmt does. Additionally it checks unused imports

代码性能审查

  • bodyclose - checks whether HTTP response body is closed successfully
  • maligned - Tool to detect Go structs that would take less memory if their fields were sorted
  • prealloc - Finds slice declarations that could potentially be preallocated

代码风格审查

  • golint - Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes
  • stylecheck - Stylecheck is a replacement for golint
  • interfacer - Linter that suggests narrower interface types
  • unconvert - Remove unnecessary type conversions
  • dupl - Tool for code clone detection
  • goconst - Finds repeated strings that could be replaced by a constant
  • depguard - Go linter that checks if package imports are in a list of acceptable packages
  • misspell - Finds commonly misspelled English words in comments
  • lll - Reports long lines
  • gosimple - Linter for Go source code that specializes in simplifying a code
  • gocritic - The most opinionated Go source code linter
  • gochecknoinits - Checks that no init functions are present in Go code
  • gochecknoglobals - Checks that no globals are present in Go code

无用代码审查

  • deadcode - Finds unused code
  • ineffassign - Detects when assignments to existing variables are not used
  • structcheck - Finds unused struct fields
  • unparam - Reports unused function parameters
  • unused - Checks Go code for unused constants, variables, functions and types
  • varcheck - Finds unused global variables and constants

2.1.2 推荐配置

linter具体功能分类,在进行ci的过程中, 配置如下:

  • 代码BUG
  • 代码复杂度
  • 代码格式化
  • 代码性能
  • 代码风格
  • 无用代码

勾选是必选项,非勾选按实际情况进行配置。具体如何配置,可以参考 .golangci.example.yml

配置好配置文件后,确认配置,通过以下命令查询当前项目配置.

# 项目目录下查询具体启用的linters设置
$: golangci-lint linters

golangci-lint配置文件的查询规则是:从当前目录,逐次向上级目录查询

2.1.3 CI集成

集成到企业 Go 项目的 CI 中就很简单,在每个项目的构建过程中,增加以下命令即可:

# 项目目录下
$: golangci-lint run

2.2 测试覆盖率审查

完成了静态代码审查的准备工作,现在看看测试代码覆盖率的方案。

2.2.1 Go项目测试覆盖率

Go 1.2 版本就已经提供了代码测试覆盖率的工具。具体操作如下:

# 项目整体测试覆盖率, 以 github.com/x-mod/routine 项目为例
$: go test -cover github.com/x-mod/routine | awk '{print $4 " " $5}'
coverage: 83.0%

具体测试覆盖率详细信息,可以通过命令行的方式也可以通过网页的方式查看,按https://dave.cheney.net提供的脚本,简单修改后如下:

# 测试覆盖率详情
cover() {
  t=$(mktemp)
  go test $COVERFLAGS -coverprofile=$t $@ && go tool cover -func=$t && unlink $t
}

# 测试覆盖率详情 web方式查看
cover-web() {
  t=$(mktemp)
  go test $COVERFLAGS -coverprofile=$t $@ && go tool cover -html=$t && unlink $t
}

具体测试覆盖率详情cover-web github.com/x-mod/routine, 效果如图:

cover

命令行效果,读者可以自己测试一下 cover github.com/x-mod/routine

2.2.3 CI集成

如何集成到 CI 流程,也很简单了,只需要做一件事: 设置好测试覆盖率的通过值,比对项目的测试覆盖率的值。简单提供一下取值比较的脚本函数:

cover-val(){
  cover $@ | grep ^total | awk '{print $3}' | awk -F. '{print $1}'
}

cover-cmp(){
  cmp=$1
  shift
  val=$(cover-val $@)
  num=$(($val+0))
  if [ $cmp -gt  $num ]; then
    echo "coverage insufficient."
    return 1
  fi
  return 0
}

下面在CI过程直接就可以增加相应的覆盖率审查操作:

# 覆盖率 > 60%
$: cover-cmp 60 github.com/x-mod/routine ; echo $?
0

# 覆盖率 > 90%
$: cover-cmp 90 github.com/x-mod/routine ; echo $?
coverage insuffient.
1

# Go 项目路径 
$: cover-cmp 60 ./...

具体脚本可参考: liujianping/oh-my-zsh/plugins/alias

3 参考链接