golangci-lint 代码检查

Golang
559
0
0
2022-06-03

静态代码检查

静态代码检查是一个老生常态的问题,它能很大程度上保证代码质量。Go 语言自带套件为我们提供了静态代码分析工具 vet,它能用于检查 go 项目中可以通过编译但仍可能存在错误的代码。静态代码检查是一个老生常态的问题,它能很大程度上保证代码质量。Go 语言自带套件为我们提供了静态代码分析工具 vet,它能用于检查 go 项目中可以通过编译但仍可能存在错误的代码。

以上谈到的工具,我们可以称之为 linter。在维基百科是如下定义 lint 的:

在计算机科学中,lint 是一种工具程序的名称,它用来标记源代码中,某些可疑的、不具结构性(可能造成bug)的段落。它是一种静态程序分析工具,最早适用于C语言,在UNIX平台上开发出来。后来它成为通用术语,可用于描述在任何一种计算机程序语言中,用来标记源代码中有疑义段落的工具。

而在 Go 语言领域, golangci-lint 是一个集大成者的 linter 框架。它集成了非常多的 linter,包括了上文提到的几个,合理使用它可以帮助我们更全面地分析与检查 Go 代码。golangci-lint 所支持的 linter 项可以查看页面 golangci-lint.run/usage/linters/#g...

使用 golangci-lint

下载

go get github.com/golangci/golangci-lint/cmd/golangci-lint@latest

检查安装成功

$ golangci-lint version
golangci-lint has version v1.41.1

查看帮助文档

golangci-lint help linters

  • 默认生效的 linters

image-20210806202635527

  • 默认不生效的 linters

image-20210806202808804

  • linters 分类

image-20210806202922712

可以看出,golangci-lint 框架支持的 linter 非常全面,它包括了 bugs、error、format、unused、module 等常见类别的分析 linter。

实例

下面用实例展示一下,目录结构如下:

.

├── exam

│ └── exam.go

├── go.mod

└── main.go

其中,main.go 代码如下:

package main

import "fmt"

func main() {
    s1 := "this is a string"
    fmt.Printf("inappropriate formate %s\n", &s1)

    i := 1
    fmt.Println(i != 0 || i != 1)

    arr := []int{1, 2, 3}
    for _, i := range arr {
        go func() {
            fmt.Println(i)
        }()
    }
}

exam.go代码如下:

package exam

import "fmt"

func check() {
    t := unexistType{}
    fmt.Println(t)
}

func unused() {
    i := 1
}

这二个文件的源代码都存在一些问题,此时,我们通过 golangci-lint 工具来对源码文件进行检查

image-20210806203448071

可以看到,我们在程序根目录中执行 golangci-lint run 命令,它等效于 golangci-lint run ./... 。此时,它将 main.gotypecheckDemo.go 中存在的潜在问题都检测到了,并标记了是何种 linter 检测(这里是 typecheck 和 govet 两种)到的。

当然,也可以通过命令 golangci-lint run dir1 dir2/... dir3/file1.go 对某特定的文件或文件夹进行分析。

灵活运用命令选项
  • 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

如上命令代表的就是所有属于 bugserror 分类的 linter 生效。

  • 更多命令选项,可以通过 golangci-lint run -h 查看
配置文件

当然,如果我们要为项目配置 golangci-lint,最好的方式还是配置文件。golangci-lint 在当前工作目录按如下顺序搜索配置文件。

  • .golangci.yml
  • .golangci.yaml
  • .golangci.toml
  • .golangci.json

在 golangci-lint 官方文档 golangci-lint.run/usage/configurat... 中,提供了一个示例配置文件,非常地详细,在这其中包含了所有支持的选项、描述和默认值。

这里给出一个简单的配置文档实例:

linters-settings:
    errcheck:
        check-type-assertions: true

goconst:
    5    min-len: 2
    6    min-occurrences: 3

    gocritic:
        enabled-tags:
          - diagnostic
          - experimental
          - opinionated
          - performance
          - style
    gove:
        check-shadowing: truenolintlint:
        require-explanation: truerequire-specific: true

linters:

  disable-all: trueenable:
      - bodyclose
      - deadcode
      - depguard
      - dogsled
      - dupl
      - errcheck
      - exportloopref
      - exhaustive
      - goconst
      - gocritic
      - gofmt
      - goimports
      - gomnd
      - gocyclo
      - gosec
      - gosimple
      - govet
      - ineffassign
      - misspell
      - nolintlint
      - nakedret
      - prealloc
      - predeclared
      - revive
      - staticcheck
      - structcheck
      - stylecheck
      - thelper
      - tparallel
      - typecheck
      - unconvert
      - unparam
      - varcheck
      - whitespace
      - wsl


run:
  issues-exit-code: 1

使用 pre-commit hook

在项目开发中,我们都会使用到 git,因此我们可以将代码静态检查放在一个 git 触发点上,而不用每次写完代码手动去执行 golangci-lint run 命令。这里,我们就需要用到 git hooks。

git hooks

git hooks 是 git 的一种钩子机制,可以让用户在 git 操作的各个阶段执行自定义的逻辑。git hooks 在项目根目录的 .git/hooks 下面配置,配置文件的名称是固定的,实质上就是一个个 shell 脚本。根据 git 执行体,钩子被分为客户端钩子和服务端钩子两类。

客户端钩子包括:pre-commitprepare-commit-msgcommit-msgpost-commit等,主要用于控制客户端git的提交工作流。服务端钩子:pre-receivepost-receiveupdate,主要在服务端接收提交对象时、推送到服务器之前调用。

注意,以 .sample 结尾的文件名是官方示例,这些示例脚本是不会执行的,只有重命名后才会生效(去除 .sample 后缀)。

而 pre-commit 正如其名一样,它在 git add 提交之后,运行 git commit 时执行,脚本执行没报错就继续提交,反之就驳回提交的操作。

pre-commit

试想,如果我们同时开发多个项目,也许项目的所采用的的编程语言并不一样,那么它们所需要的 git hooks 将不一致,此时我们是否要手动给每个项目都配置一个单独的 pre-commit 脚本呢,或者我们是否要去手动下载每一个钩子脚本呢。

实际上,并不需要这么麻烦。这里就引出了 pre-commit 框架,它是一个与语言无关的用于管理 git hooks 钩子脚本的工具

  • 安装
$ pip install pre-commit
或者
$ curl https://pre-commit.com/install-local.py | python -
或者
$ brew install pre-commit
  • 安装成功
$ pre-commit --version
pre-commit 1.20.0
  • 编写配置文件

首先我们在项目根目录下新建一个 .pre-commit-config.yaml 文件,这个文件我们可以通过 pre-commit sample-config 得到最基本的配置模板,通过 pre-commit 支持的 hooks 列表 https://pre-commit.com/hooks.html中,我们找到了 golangci-lint。

image-20210809213007293

因此,使用 golangci-lint 的 .pre-commit-config.yaml 配置内容如下

repos: 
  - repo: https://github.com/golangci/golangci-lint 
    rev: v1.41.1 # the current latest version 
    hooks: 
      - id: golangci-lint
  • 安装 git hook 脚本
$ pre-commit install
pre-commit installed at .git/hooks/pre-commit

此时,生成了新的 Python 语言编写的 .git/hooks/pre-commit 钩子文件。

  • git commit 触发 golangci-lint 检查**
go-web git:(master) ✗ git add .  
➜  go-web git:(master) ✗ git commit -m "golangci-lint test"
golangci-lint............................................................Failed
- hook id: golangci-lint
- exit code: 1

router.go:8:6: `CollectRoute` is unused (deadcode)
func CollectRoute(r *gin.Engine) *gin.Engine {
     ^
model/time.go:32:15: func `Time.local` is unused (unused)
func (t Time) local() time.Time {
              ^
main.go:17:14: bools: suspect or: i != 0 || i != 1 (govet)
        fmt.Println(i != 0 || i != 1)
                    ^
main.go:22:16: loopclosure: loop variable i captured by func literal (govet)
                        fmt.Println(i)
                                    ^
main.go:14:2: printf: fmt.Printf format %s has arg &s1 of wrong type *string (govet)
        fmt.Printf("inappropriate formate %s\n", &s1)
        ^
main.go:43:4: printf: fmt.Println call has possible formatting directive %v (govet)
                        fmt.Println("startup service failed, err:%v\n", err)

总结

代码质量是每位开发者都必须重视的问题,golangci-lint 提供的一系列 linter 插件能够在很大程度上帮助 Gopher 及时发现与解决潜在 bug。同时,借助 golangci-lint 还可以有效规范项目组内的代码风格,减轻 code review 的心智负担,希望 能有效利用它。

git-commit 工具通过配置文件生成 git hooks 所需要的 pre-commit 钩子脚本,这可以将通过 golangci-lint 的静态代码检查工作,由手工动作转化为自动化流程。上文关于 git-commit 的介绍比较简单,想更详细探究可以直接去官网 pre-commit.com/index.html 学习。