likes
comments
collection
share

Golang 聊聊最经典的 linter—— golangci-lint 怎么用

作者站长头像
站长
· 阅读数 56

为什么需要 lint 工具

在国内互联网超高速迭代的研发效率催动下,其实线上服务是很容易出问题的。紧接着就是复盘,跟进 TODO。最后不了了之,唯一能保证的只是把造成事故的直接原因给扼杀掉。

Why?

因为落地难!此前在 一个程序员的成长思考 里我就提过,靠人工,靠意识来保障的制度是非常脆弱的,基本不可能落地。

要改变流程,一定是痛苦的,需要有卡点,需要有自动化流程。大家虽然痛苦,但遵守规范来做,并不断发现工具的问题,修复,迭代,到最后慢慢变熟练,流程才能固化。

Code Review 尤其是如此!

  • 发现需要review的代码里 import 没有按照规范,说不说?
  • 发现有一堆 magic number,说不说?
  • 发现一堆包内使用的小函数全是大写开头的,说不说?
  • 发现有 deadcode,说不说?

说!有什么不能说的,reviewer 不就是来指出问题的么?

但是,总要有个但是。像上面这样列举的点,对于没有形成非常好规范的 Gopher 来说,可能一个300行的 Merge Request 里面能有二三十处,reviewer 的关注点全在这些事情上,哪儿还能有精力分配给真正重要的业务逻辑呢?这对 reviewer 来说是巨大的时间浪费。可如果不指出来,就又走入了【破窗效应】的循环里。因为有一个妥协了,就可以再妥协一个,直到最后,慢慢代码变成了【屎山】,放弃吧,咱开个新库,整体大重构。

这样的问题的解决其实很简单:自动化流程,用 lint 卡点来限制 Merge Request,没有过卡点的一律不 review,由 committer 自己来解决代码风格问题。如果觉得某一条规则有问题,提出来,我们调整 lint 规则。如果确实无法达到标准,加上 nolint,都ok,但一定要把这一步交给自动化流程。

这样带来的好处是很鲜明:

  • 开发者自身可以学习到很多规范,知道怎样写是最有利于长远维护,有利于可读性的,自身素质提高;
  • 降低 reviewer 心智负担,只关注整体代码,接口,流程,测试即可,不必被基础代码风格问题所耽误;
  • 长远看维护更容易,把低级问题提前消除,有助于团队合作;
  • 形成正循环,有后续规范了,先添加 lint 规则,形成卡点,非常容易落地,有问题了改工具配置即可,而不是靠人工。

Golang 有哪些 lint 工具

作为 Gopher,我们有官方提供的 go fmt 工具能够完成基础的格式调整(当然,这远远不够),开源社区里提供的各种 linter 还是非常多,而且有效的。

这里也推荐一个 linter 列表:github.com/golangci/aw…

  • gofmt,Go SDK自带的代码格式检查工具,用于检查缩进、文件尾是否有空行等。go fmt命令(即gofmt -l -w)可使用该工具格式化代码。
  • go vet,Go SDK自带的可疑代码检查工具,检查unsafe.Pointer的错误使用、unreacheable code等可能有问题的代码。
  • golangci-lint,社区提供的linters聚合运行工具,内置了多个linter(代码检查工具),支持配置化&并行运行。

具体的 lint 规则非常多,而且是一个动态增加的过程,作为业务研发,我们希望能有一个统一的载体,让我们可以直接用一个 linter 就能运行各种规则,并且要可配置。这方面业界公认最出色的还是 golangci-lint,下面我们就来了解一下。

golangci-lint

golangci-lint是一个Go linters aggregator,并不是一个linter,其作用在于将每个独立的linter聚合起来得到更好的体验和满足不同的需求。

特色功能

  • 并行跑各个 linter,重复利用 Golang 的构建缓存,并缓存分析结果;
  • 提供 yaml 配置,自定义你需要的 linter 以及规则;
  • 跟 VSCode,Sublimt Text, Golang, Emacs, Vim, GitHub Actions 进行集成,你可以开箱即用;
  • 自带常用 linter,无需开发者再次安装;
  • 输出的问题有颜色区分,标识源码所在行以及出问题的标识符。

感兴趣的同学也可以参考官方文档 golangci-lint.run/usage/quick…

安装指南

如果是希望在 Github 用,可以走 Github Action 直接来限制,大大简化接入流程,参考 官方流程

这里我们看一下本地怎样跑代码,在提 MR 之前就发现问题。

  • Linux
# binary will be $(go env GOPATH)/bin/golangci-lint
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin 
  • MacOS
brew install golangci-lint
brew upgrade golangci-lint

安装完成后,执行 golangci-lint --version 就可以看到版本信息,代表安装成功。详细流程

支持的linter

使用 golangci-lint help linters 命令就可以看到所有支持的 linter

$ golangci-lint help linters
Enabled by default linters:
deadcode: Finds unused code [fast: false, auto-fix: false]
errcheck: Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false]
gosimple (megacheck): Linter for Go source code that specializes in simplifying code [fast: false, auto-fix: false]
govet (vet, vetshadow): Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string [fast: false, auto-fix: false]
ineffassign: Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
staticcheck (megacheck): It's a set of rules from staticcheck. It's not the same thing as the staticcheck binary. The author of staticcheck doesn't support or approve the use of staticcheck as a library inside golangci-lint. [fast: false, auto-fix: false]
typecheck: Like the front-end of a Go compiler, parses and type-checks Go code [fast: false, auto-fix: false]
unused (megacheck): Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false]
varcheck: Finds unused global variables and constants [fast: false, auto-fix: false]

默认配置里生效的规则如下:

Golang 聊聊最经典的 linter—— golangci-lint 怎么用

建议大家至少对这里涉及的 linter 设置为必须通过,如果连 govet, errcheck 这些都过不了,流到线上一定会给以后埋坑。

在此之外,业务可以根据自身诉求,从 linter 列表中选择符合要求的规则。这里建议大家默认也加一条圈复杂度的规则: gocyclo,它代表圈复杂度,一般我们希望控制在 10-20。再高就说明函数拆分有问题,或者里面的实现比较冗余。

linters-settings:
  gocyclo:
    # Minimal code complexity to report.
    # Default: 30 (but we recommend 10-20)
    min-complexity: 10

更多细节的 linter 配置参照说明文档:golangci-lint.run/usage/linte…

实战用法

golangci-lint run // 等价于 golangci-lint run ./...
golangci-lint run dir1 dir2/... dir3/file1.go // 选择部分目录或者文件执行
golangci-lint linters // 查看开启的 linter

运行 golangci-lint linters 的结果我们来看一下:

Golang 聊聊最经典的 linter—— golangci-lint 怎么用

如果我们希望自己来指定规则,而不是走默认规则,需要怎么配置呢?

首先,在你的项目根目录添加配置文件,用下面哪一个后缀都ok:

  • .golangci.yml
  • .golangci.yaml

配置文件的格式如下(详细参考说明文档)

# Options for analysis running.
run:
  # See the dedicated "run" documentation section.
  option: value
# output configuration options
output:
  # See the dedicated "output" documentation section.
  option: value
# All available settings of specific linters.
linters-settings:
  # See the dedicated "linters-settings" documentation section.
  option: value
linters:
  # See the dedicated "linters" documentation section.
  option: value
issues:
  # See the dedicated "issues" documentation section.
  option: value
severity:
  # See the dedicated "severity" documentation section.
  option: value

这里我们主要关注三个:

  • run:控制的是lint运行的选项,如并发数,超时时间,是否包含test文件,需要忽略的路径,文件,Golang 版本;

  • linters:选择你希望启用的linter,示例:

linters:
  # Disable all linters.
  # Default: false
  disable-all: true
  # Enable specific linter
  # https://golangci-lint.run/usage/linters/#enabled-by-default-linters
  enable:
    - gocognit
    - goconst
    - gocritic
    - gocyclo
    - godot
    - godox
    - goerr113
    - gofmt
    - gofumpt
    - tenv
    - whitespace
    - wrapcheck
    - wsl
  # Enable all available linters.
  # Default: false
  enable-all: true
  # Disable specific linter
  # https://golangci-lint.run/usage/linters/#disabled-by-default-linters--e--enable
  disable:
    - asasalint
    - asciicheck
    - bidichk
    - bodyclose
    - containedctx
    - contextcheck
    - cyclop
    - deadcode
    - decorder
    - wsl
  # Enable presets.
  # https://golangci-lint.run/usage/linters
  presets:
    - bugs
    - comment
    - complexity
    - test
    - unused
  # Run only fast linters from enabled linters set (first run won't be fast)
  # Default: false
  fast: true
  • linters-settings:具体每个 linter 的配置,类似我们上面对 gocyclo 配置最低圈复杂度。

我们在工作路径增加.golangci.yml ,配置自定义的规则。

linters:
  # Disable all linters.
 # Default: false
disable-all: true
  # Enable specific linter
 # https://golangci-lint.run/usage/linters/#enabled-by-default-linters
enable:
    # default linter
- deadcode
    - errcheck
    - gosimple
    - govet
    - ineffassign
    - staticcheck
    - typecheck
    - unused
    - varcheck
    # 新增 linter
- gocyclo
    - gofmt
    - goimports
  # Run only fast linters from enabled linters set (first run won't be fast)
 # Default: false
fast: true

linters-settings:
  gocyclo:
    # Minimal code complexity to report.
    # Default: 30 (but we recommend 10-20)
    min-complexity: 10

再执行 golangci-lint run 之后,就能看到输出的结果了。

Golang 聊聊最经典的 linter—— golangci-lint 怎么用

nolint

有些时候因为一些特殊原因,比如某个函数无法更改,或至少在某个时段暂时达不到 linter 标准,我们希望跳过格式检查,怎么办呢?

这个时候可以使用 nolint 命令。

nolint 有两个层面的问题需要考虑:

  1. 规则范围;
  2. 生效范围。

前者指的是你需要排除哪些 linter 规则的影响,比如我只想针对 gocyclo 策略忽略 linter,就可以直接加上 //nolint:gocyclo 即可。

后者描述的是规则生效范围,比如是针对一行,还是一个代码块,还是一个文件。事实上 nolint 对这些层级都提供了支持。

  • 行级别
var bad_name int //nolint:all

var bad_name int //nolint:golint,unused

注意,这里 all 代表着所有 linter 规则都忽略。你也可以指定要忽略的 linter,用逗号分隔。

  • 函数/代码块级别
//nolint:all
func allIssuesInThisFunctionAreExcluded() *string {
  // ...
}

//nolint:govet
var (
  a int
  b int
)
  • 文件级别
//nolint:unparam
package pkg

同时,仅仅一个 nolint 很有可能是迷惑性的,就像很多 TODO, FIXME,我们也不知道谁留的,不知道什么时候 DO,FIX。不知道什么时候让这个函数/行/文件达到 linter 要求。

所以,建议在 nolint 之后加上说明,类似这样:

//nolint:gocyclo // This legacy function is complex but the team too busy to simplify it
func someLegacyFunction() *string {
  // ...
}

machine-readable comments should have no space by Go convention

最后一个关键点,注意我们上面的 nolint 都是紧跟着 // 注释符号的,这也是 Go 的规范,机器识别的注释不应该有空格(开发者自己看的是需要加一个空格的)。

总结

今天我们对 golangci-lint 的用法,以及lint这件事整体的定位进行了学习。大家只要记住它是一个 linter 的聚合器即可,具体用法大家可以慢慢操练起来。要形成落地的规范,一定要有卡点,原则上需要修改所有报出的问题后才可以 submit。

建议大家关注 golangci-lint 官网 以及 源码仓库 更新,把 lint 用起来,提高组里代码水平,降低 reviewer 心智负担。

同时,golangci-lint 也支持社区成员贡献新的 linter,这里我们不再多说,感兴趣的同学可以来这里看一下:golangci-lint.run/contributin…

感谢阅读!有想法欢迎在评论区交流!

转载自:https://juejin.cn/post/7130188153792495630
评论
请登录