Gopher一定要會(huì)的代碼自動(dòng)化檢查
本文講解如何通過 golangci-lint 和 pre-commit 兩大框架,利用 git hooks 實(shí)現(xiàn) Go 語言 git commit 的代碼自動(dòng)化審查。
靜態(tài)代碼檢查
靜態(tài)代碼檢查是一個(gè)老生常態(tài)的問題,它能很大程度上保證代碼質(zhì)量。Go 語言自帶套件為我們提供了靜態(tài)代碼分析工具 vet,它能用于檢查 go 項(xiàng)目中可以通過編譯但仍可能存在錯(cuò)誤的代碼,不了解的讀者可以查看之前 文章 的介紹。除了 go vet,不少 Gopher 也許還知道用于自動(dòng)導(dǎo)包的 goimports 工具,用于格式化代碼的 gofmt,還有已經(jīng)停止更新的用于檢查代碼命令錯(cuò)誤等的 golint 工具。
以上談到的工具,我們可以稱之為 linter。在這里,我們首先需要知道什么是 lint。在維基百科是如下定義 lint 的:
在計(jì)算機(jī)科學(xué)中,lint 是一種工具程序的名稱,它用來標(biāo)記源代碼中,某些可疑的、不具結(jié)構(gòu)性(可能造成bug)的段落。它是一種靜態(tài)程序分析工具,最早適用于C語言,在UNIX平臺(tái)上開發(fā)出來。后來它成為通用術(shù)語,可用于描述在任何一種計(jì)算機(jī)程序語言中,用來標(biāo)記源代碼中有疑義段落的工具。
而在 Go 語言領(lǐng)域, golangci-lint 是一個(gè)集大成者的 linter 框架。它集成了非常多的 linter,包括了上文提到的幾個(gè),合理使用它可以幫助我們更全面地分析與檢查 Go 代碼。golangci-lint 所支持的 linter 項(xiàng)可以查看頁面 https://golangci-lint.run/usage/linters/#golint
使用 golangci-lint
下載
1go get github.com/golangci/golangci-lint/cmd/golangci-lint@latest
檢查安裝成功
1$ golangci-lint version
2golangci-lint has version v1.41.1 built from (unknown, mod sum: "h1:KH28pTSqRu6DTXIAANl1sPXNCmqg4VEH21z6G9Wj4SM=") on (unknown)
查看幫助文檔
1$ golangci-lint help linters
默認(rèn)生效的 linters

默認(rèn)不生效 linters

linters 的分類

可以看出,golangci-lint 框架支持的 linter 非常全面,它包括了 bugs、error、format、unused、module 等常見類別的分析 linter。
實(shí)例
下面來展示使用示例,現(xiàn)有以下項(xiàng)目結(jié)構(gòu)代碼
1.
2├── go.mod
3├── main.go
4└── typecheck
5 └── typecheckDemo.go
其中 main.go 中的代碼如下
1package main
2
3import (
4 "fmt"
5)
6
7func main() {
8 s1 := "this is a string"
9 fmt.Printf("inappropriate formate %s\n", &s1)
10
11 i := 1
12 fmt.Println(i != 0 || i != 1)
13
14 arr := []int{1, 2, 3}
15 for _, i := range arr {
16 go func() {
17 fmt.Println(i)
18 }()
19 }
20}
typecheckDemo.go 中的代碼
1package typecheck
2
3import "fmt"
4
5func check() {
6 t := unexistType{}
7 fmt.Println(t)
8}
9
10func unused() {
11 i := 1
12}
這兩個(gè)源碼文件中的代碼都是存在一些問題的。此時(shí),我們通過 golangci-lint 工具來對(duì)源碼文件進(jìn)行檢查

可以看到,我們?cè)诔绦蚋夸浿袌?zhí)行 golangci-lint run 命令,它等效于 golangci-lint run ./... 。此時(shí),它將 main.go 和 typecheckDemo.go 中存在的潛在問題都檢測(cè)到了,并標(biāo)記了是何種 linter 檢測(cè)(這里是 typecheck 和 govet 兩種)到的。
當(dāng)然,也可以通過命令 golangci-lint run dir1 dir2/... dir3/file1.go 對(duì)某特定的文件或文件夾進(jìn)行分析。
靈活運(yùn)用命令選項(xiàng)
golangci-lint 可以通過
-E/--enable去開啟指定 linter,或者-D/--disable禁止指定 linter。
1golangci-lint run --disable-all -E errcheck
如上命令代表的就是除了 errcheck 的 linter,禁止其他所有的 linter 生效。
golangci-lint 還可以通過
-p/--preset指定一系列 linter 開啟。
1golangci-lint run -p bugs -p error
如上命令代表的就是所有屬于 bugs 和 error 分類的 linter 生效。
更多命令選項(xiàng),可以通過
golangci-lint run -h查看
配置文件
當(dāng)然,如果我們要為項(xiàng)目配置 golangci-lint,最好的方式還是配置文件。golangci-lint 在當(dāng)前工作目錄按如下順序搜索配置文件。
.golangci.yml.golangci.yaml.golangci.toml.golangci.json
在 golangci-lint 官方文檔 https://golangci-lint.run/usage/configuration/#config-file 中,提供了一個(gè)示例配置文件,非常地詳細(xì),在這其中包含了所有支持的選項(xiàng)、描述和默認(rèn)值。
在這里給出一個(gè)比較不錯(cuò)的配置示例文檔
1linters-settings:
2 errcheck:
3 check-type-assertions: true
4 goconst:
5 min-len: 2
6 min-occurrences: 3
7 gocritic:
8 enabled-tags:
9 - diagnostic
10 - experimental
11 - opinionated
12 - performance
13 - style
14 govet:
15 check-shadowing: true
16 nolintlint:
17 require-explanation: true
18 require-specific: true
19
20linters:
21 disable-all: true
22 enable:
23 - bodyclose
24 - deadcode
25 - depguard
26 - dogsled
27 - dupl
28 - errcheck
29 - exportloopref
30 - exhaustive
31 - goconst
32 - gocritic
33 - gofmt
34 - goimports
35 - gomnd
36 - gocyclo
37 - gosec
38 - gosimple
39 - govet
40 - ineffassign
41 - misspell
42 - nolintlint
43 - nakedret
44 - prealloc
45 - predeclared
46 - revive
47 - staticcheck
48 - structcheck
49 - stylecheck
50 - thelper
51 - tparallel
52 - typecheck
53 - unconvert
54 - unparam
55 - varcheck
56 - whitespace
57 - wsl
58
59run:
60 issues-exit-code: 1使用 pre-commit hook
在項(xiàng)目開發(fā)中,我們都會(huì)使用到 git,因此我們可以將代碼靜態(tài)檢查放在一個(gè) git 觸發(fā)點(diǎn)上,而不用每次寫完代碼手動(dòng)去執(zhí)行 golangci-lint run 命令。這里,我們就需要用到 git hooks。
git hooks
git hooks 是 git 的一種鉤子機(jī)制,可以讓用戶在 git 操作的各個(gè)階段執(zhí)行自定義的邏輯。git hooks 在項(xiàng)目根目錄的 .git/hooks 下面配置,配置文件的名稱是固定的,實(shí)質(zhì)上就是一個(gè)個(gè) shell 腳本。根據(jù) git 執(zhí)行體,鉤子被分為客戶端鉤子和服務(wù)端鉤子兩類。
客戶端鉤子包括:pre-commit、prepare-commit-msg、commit-msg、post-commit等,主要用于控制客戶端git的提交工作流。服務(wù)端鉤子:pre-receive、post-receive、update,主要在服務(wù)端接收提交對(duì)象時(shí)、推送到服務(wù)器之前調(diào)用。
注意,以 .sample 結(jié)尾的文件名是官方示例,這些示例腳本是不會(huì)執(zhí)行的,只有重命名后才會(huì)生效(去除 .sample 后綴)。

而 pre-commit 正如其名一樣,它在 git add 提交之后,運(yùn)行 git commit 時(shí)執(zhí)行,腳本執(zhí)行沒報(bào)錯(cuò)就繼續(xù)提交,反之就駁回提交的操作。
pre-commit
試想,如果我們同時(shí)開發(fā)多個(gè)項(xiàng)目,也許項(xiàng)目的所采用的的編程語言并不一樣,那么它們所需要的 git hooks 將不一致,此時(shí)我們是否要手動(dòng)給每個(gè)項(xiàng)目都配置一個(gè)單獨(dú)的 pre-commit 腳本呢,或者我們是否要去手動(dòng)下載每一個(gè)鉤子腳本呢。
實(shí)際上,并不需要這么麻煩。這里就引出了 pre-commit 框架,它是一個(gè)與語言無關(guān)的用于管理 git hooks 鉤子腳本的工具(雖然采用 Python 開發(fā),但不止于 Python )。
安裝
1$ pip install pre-commit
2或者
3$ curl https://pre-commit.com/install-local.py | python -
4或者
5$ brew install pre-commit
安裝成功
1$ pre-commit --version
2pre-commit 1.20.0
編寫配置文件
首先我們?cè)陧?xiàng)目根目錄下新建一個(gè) .pre-commit-config.yaml 文件,這個(gè)文件我們可以通過 pre-commit sample-config 得到最基本的配置模板,通過 pre-commit 支持的 hooks 列表 https://pre-commit.com/hooks.html 中,我們找到了 golangci-lint。

因此,使用 golangci-lint 的 .pre-commit-config.yaml 配置內(nèi)容如下
1repos:
2- repo: https://github.com/golangci/golangci-lint
3 rev: v1.41.1 # the current latest version
4 hooks:
5 - id: golangci-lint
安裝 git hook 腳本
運(yùn)行 pre-commit install 命令根據(jù)配置文件安裝
1$ pre-commit install
2pre-commit installed at .git/hooks/pre-commit
此時(shí),生成了新的 Python 語言編寫的 .git/hooks/pre-commit 鉤子文件。
git commit 觸發(fā) golangci-lint 檢查

首次運(yùn)行時(shí),由于 pre-commit 沒有 golangci-lint 的環(huán)境,會(huì)初始化下載安裝相關(guān)依賴。在下一次 git-commit 的時(shí)候,就不會(huì)有前三行信息了。
如上圖所示,報(bào)錯(cuò)內(nèi)容和我們手動(dòng)執(zhí)行 golangci-lint run 命令輸出的一樣,只有當(dāng)我們將代碼更改正確,才能順利通過檢查,從而 commit 成功。
總結(jié)
代碼質(zhì)量是每位開發(fā)者都必須重視的問題,golangci-lint 提供的一系列 linter 插件能夠在很大程度上幫助 Gopher 及時(shí)發(fā)現(xiàn)與解決潛在 bug。同時(shí),借助 golangci-lint 還可以有效規(guī)范項(xiàng)目組內(nèi)的代碼風(fēng)格,減輕 code review 的心智負(fù)擔(dān),希望 Gopher 們能有效利用它。
git-commit 工具通過配置文件生成 git hooks 所需要的 pre-commit 鉤子腳本,這可以將通過 golangci-lint 的靜態(tài)代碼檢查工作,由手工動(dòng)作轉(zhuǎn)化為自動(dòng)化流程。上文關(guān)于 git-commit 的介紹比較簡(jiǎn)單,想更詳細(xì)探究的讀者可以直接去官網(wǎng) https://pre-commit.com/index.html 學(xué)習(xí)。其實(shí),這種自動(dòng)化流程我們可以擴(kuò)展得更廣,例如我們可以利用 golangci-lint 規(guī)則防止從項(xiàng)目中拉取不符合標(biāo)準(zhǔn)的代碼進(jìn)入本地代碼庫,這可以在持續(xù)集成 CI 過程中添加它來實(shí)現(xiàn)自動(dòng)化。鑒于篇幅原因,CI 部分的利用,留待讀者自行探究了。
------------------- End -------------------
往期精彩文章推薦:

歡迎大家點(diǎn)贊,留言,轉(zhuǎn)發(fā),轉(zhuǎn)載,感謝大家的相伴與支持
想加入Go學(xué)習(xí)群請(qǐng)?jiān)诤笈_(tái)回復(fù)【入群】
萬水千山總是情,點(diǎn)個(gè)【在看】行不行
