Githook实践以代码规范检测插件golangci-lint为例

2,774 阅读3分钟

「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

引子

"kovogo哥, 我怎么提交不了代码了"

没想到工作两年就已经进入哥字辈了, 往旁边一看旁边新来的小伙子正常对着屏幕发愁, 一看控制台的报错,哦原来是golangci-lint打回了提交, 原因是某一行的代码太长了超过了限制。

那么在提交代码对代码规范进行检查是怎么做到的? 这就需要我们来回顾一下githook的知识了。

githook

什么是githook?

一般来说只要是上了规模的软件/中间件/开源框架或多或少的会提供一些hook, 供开发人员使用。而这些hook的作用就是在这些框架/软件中引入开发人员编写代码, 从而改变软件的表现形式或行为逻辑。

githook就是git是向开发者提供的在git执行重要的操作引入开发者的代码的功能,比如上文提到在commit时引入代码检查的功能就是通过githook实现的。

每个git仓库在初始化的时候, 都会在.git目录下创建一个hooks目录,用来存放开发人员自定义的hook脚本(shell脚本), 脚本文件名即hook名。

windows 环境安装git时会自带git bash, 因此无需要担心脚本的兼容性

hook脚本返回0时表示放行对应的操作, 返回非0值表示拒绝此操作。

每个程序在退出的时候都会有返回值, 该返回值通常用来表示程序是否执行成功.

在Linux shell中我们可以通过, $?来获取上一个程序的返回值.

image.png

如上图所示, 我们尝试访问一个不存在的路径, ls命令的返回值是2.

我们可以在.git/hooks中看到如下文件:

image.png

上图中.sample文件都是git官方提供的参考, 去掉.sample后缀该钩子就会起效, 记住: 文件名即钩子名

如果需要在提交代码时对代码进行检测,我们就需要为pre-commit钩子编写代码规范检测脚本。

更多详细信息可参见官方文档

golintci-lint

由于golint已经被谷歌抛弃不再维护了, 我们使用了golangci-lint对代码规范进行检查。

安装

golangci-lint官网给出的安装方式有两种:

  • 二进制安装, 直接下载对应平台的可执行文件
  • 如果版本小于1.16使用go get下载源文件并编译安装, 大于1.16则使用go install
# Go 1.16+
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.43.0

# Go version < 1.16
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint@v1.43.0

以windows平台为例, 我的golang版本是1.16.10

image.png

因此我们使用go install的方式进行安装

image.png

使用

使用run命令即可对目标目录的代码进行检测

golangci-lint run [目录]

使用-h可以查看run命令的更多帮助信息

golangci-lint run -h

image.png

值得注意的选项有:

  • --issues-exit-code 代码检测失败返回的错误码, 默认是1
  • -c, --config PATH 用于定制代码检测项目的配置文件(yaml格式)
  • --skip-files 要跳过的文件
  • --skip-dirs 要跳过的目录
  • --enable 要启用的代码检测项目

定制代码检测

上文提到, 我们可以通过--config来定制代码检测项目的配置, 其格式如下:

linters-settings:
  cyclop: # 代码检测项目名通常会对应到某个插件
    # 最大的复杂度
    max-complexity: 10
    # 每个包允许的最大复杂度
    package-average: 0.0
    # 是否跳过测试文件
    skip-tests: false

更多信息参考官方文档

githook + golangci-lint

以windows平台为例, 我们新建一个项目

image.png

然后新建一个代码文件, 随便写点乱七八糟的代码:

func main() {
	for i := 0; i < 1024; i++ {
		for j := 0; j < 1024; j++ {
			for n := 0; n < 1024; n++ {
				fmt.Printf("%d-%d-%s", i, j, n)
			}
		}
	}
}

golangci-lint检测到了Printf传了错误的参数

image.png

我们再往项目中里面加点有"坏味道"的代码, 如下所示:(示例来自cyclop项目的测试文件)

func T() {
	i := 1
	if i > 2 {
		if i > 2 {
		}
		if i > 2 {
		}
		if i > 2 {
		}
		if i > 2 {
		}
	} else {
		if i > 2 {
		}
		if i > 2 {
		}
		if i > 2 {
		}
		if i > 2 {
		}
	}

	if i > 2 {
	}
}

再次进行代码检测, 注意此时需要启用圈复杂度检测插件cyclop

image.png

如果我们想要让代码通过检测,就需要使用到上文提到的使用配置文件自定义代码检测项目了。

在项目中创建lint.yaml, 并编写配置项如下

linters-settings:
  cyclop:
    # 最大的复杂度
    max-complexity: 15
    # 每个包允许的最大复杂度
    package-average: 0.0
    # 是否跳过测试文件
    skip-tests: false

再次运行并指定配置文件, 此时已不再提示圈复杂度过高

image.png

pre-commit hook

利用上文的知识,我们的pre-commit hook 脚本的逻辑如下所示:

  • 检测是否安装golangci-lint, 如果没有则进行安装
  • 运行golangci-lint对代码进行检测,如果检测失败返回1成功则返回0
#!/bin/sh

echo "Start lint code"
if !(golangci-lint.exe --version); then
    go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.43.0
fi
# 目录名后跟上...表示对该目录进行递归查找
if !(golangci-lint.exe run --enable cyclop --config ./lint.yaml ./...); then
    echo "Lint fail!"
    exit 1
fi

echo "Lint success"

exit 0

尝试对代码提交,运行效果如下图所示 image.png