目录

golang 代码检测工具 golangci-lint 使用教程

背景

为什么需要 lint 工具?大部分公司都有 Code Review 制度,但是纯靠人工、靠主观意识来检测代码的质量是否合规是非常耗时、并且十分脆弱的,如果在 Code Review 之前,能够先用 lint 工具检测一遍,让开发能够先自检,减少问题代码,也能提高 Code Review 的时间和耗时

go 的 lint 工具有非常多:

  • go fmt
  • go imports
  • go vet
  • …..

但是我们总不能一个个去安装,那简直太麻烦了,所以开源社区提供了一款 linters 聚合工具:golangci-lint,集合了绝大多数的 lint 工具,可通过配置化来实现团队自定义 lint 代码质量规则,golangci-lint 是目前公认的最好的 linter 整合工具

一、安装 golangci-lint

(一)方式一:go install

go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2

(二)方式二:下载安装

github 找到自己的版本,下载安装即可

Releases · golangci/golangci-lint · GitHub

(三)验证

运行:

golangci-lint --version

输出版本即安装成功: golangci-lint has version v1.55.2

二、配置使用

在项目根目录创建.golangci.yml文件,配置说明如下

完整版本在 https://golangci-lint.run/usage/configuration/

官方参考例子 https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml

分为以下六大部分,按需配置,并不是都需要

  1. run 运行配置,比如超时,忽略文件等
  2. output 输出配置,比如格式
  3. linters-settings 检测器配置,对具体的检测器细化配置
  4. linters 开启关闭检测器
  5. issues 检测器输出报告配置,比如忽略某些检测器的输出
  6. severity 检测器敏感度配置
# 完整版本在 https://golangci-lint.run/usage/configuration/
# 官方参考例子 https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml

# 1. 运行配置,比如超时、忽略文件等
run:
  # 运行 golangci-lint 时要使用的 CPU 数量。默认值:计算机中的逻辑 CPU 数量
#  concurrency: 1

  # 分析超时,例如 30 秒、5 分钟。默认值:1m
  # Default: 1m
  timeout: 5m

  # 发现至少 n 个问题时的退出代码。默认值:1
  issues-exit-code: 1

  # 是否包含测试文件。默认值:true
  tests: false

  # 构建标记列表,所有 linter 都使用它。默认值 []。TODO:不知道有什么用
  # build-tags:
  #  - mytag

  # 要跳过的目录:不会报告它们的问题。可以在这里使用正则表达式: 'generated.*',正则表达式应用于完整路径,如果设置了路径前缀,则包括路径前缀。
  # 默认目录将独立于此选项的值进行跳过(请参阅 skip-dirs-use-default)。
  # "/" 将被替换为当前操作系统文件路径分隔符,以便在 Windows 上正常工作。
  # 默认值: []
  skip-dirs:
    - gva
    - jxykit
    - logs
    - web


    # 允许跳过目录:
    # - vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
  # Default: true
  # skip-dirs-use-default: false


  # 要跳过的文件:将分析它们,但不会报告它们的问题。
  # 没有必要包含所有自动生成的文件,我们自信地识别自动生成的文件,如果不是,请告诉我们。
  # "/" 将被替换为当前操作系统文件路径分隔符,以便在 Windows 上正常工作。
  # Default: []
  # skip-files:
  #   - ".*\\.my\\.go$"
  #   - lib/bad.go

  # 如果设置,我们将其传递给 “go list -mod={option}”。从“go help modules”:
  # 如果使用 -mod=readonly 调用,则不允许在上述 go.mod 的隐式自动更新中使用 go 命令。相反,当需要对 go.mod 进行任何更改时,它会失败。此设置对于检查 go.mod 是否不需要更新(例如在持续集成和测试系统中)最有用
  # 如果使用 -mod=vendor 调用,则 go 命令假定 vendor 目录包含依赖项的正确副本,并忽略 go.mod 中的依赖项描述。
  # 允许的值: readonly|vendor|mod
  # Default: ""
  # modules-download-mode: readonly


  # 允许运行多个并行 golangci-lint 实例。
  # If false, golangci-lint 在启动时获取文件锁定。
  # Default: false
  # allow-parallel-runners: false


  # 允许运行多个 golangci-lint 实例,但围绕一个锁序列化它们。
  # If false, 如果 golangci-lint 在启动时无法获取文件锁定,则会退出并显示错误。
  # Default: false
  # allow-serial-runners: true


  # 打印 golangci-lint 的平均和最大内存使用量和总时间。
  # Default: false
  print-resources-usage: true


  # 定义 Go 版本限制。
  # 主要与自 go1.18 以来的泛型支持有关。
  # Default: use Go version from the go.mod file, fallback on the env var `GOVERSION`, fallback on 1.17
  # go: '1.19'

# 2. 输出配置,比如格式
output:
  # Format: colored-line-number|line-number|json|colored-tab|tab|checkstyle|code-climate|junit-xml|github-actions|teamcity
  # 可以通过用逗号分隔它们来指定多个,可以通过用冒号分隔格式名称和路径来为每个它们提供输出。
  # 输出路径可以是“stdout”、“stderr”或要写入的文件的路径。
  # Example: "checkstyle:report.xml, json:stdout,colored-line-number"
  # Default: colored-line-number
  format: colored-tab

  # 打印有问题的代码行
  # Default: true
  # print-issued-lines: false

  # 在问题文本末尾打印 linter 名称。
  # Default: true
  # print-linter-name: false

  # Make issues output unique by line.
  # Default: true
  # uniq-by-line: false

  # 将前缀添加到输出文件引用。
  # Default: ""
  # path-prefix: ""

  # 对结果进行排序: filepath, line and column.
  # Default: false
  sort-results: true

# 3. 开启关闭检测器配置
linters:
  # 指定代码不检查 _ = c.Error(err.(error)) // nolint: errcheck
  # 是否禁用所有 linter。
  # Default: false
  # disable-all: true
  # Enable specific linter
  # 默认开启的 linters https://golangci-lint.run/usage/linters/#enabled-by-default
  disable-all: true
  enable:
    - funlen             # 用于检测长函数的工具。
    - goconst            # 查找可由常量替换的重复字符串。
    - gocyclo            # 计算和检查函数的圈复杂度
    - gofmt              # 代码格式化。
    - ineffassign        # 检查一些无用赋值
    - staticcheck        # 静态语法检查
    - typecheck          # 类型检查
    - goimports          # 对 imports 进行格式化排序
    - revive             # 代码修复;快速、可配置、可扩展、灵活且美观的 Go linter。golint 的直接替代品。
#    - gosimple           # Linter for Go 源代码,专门用于简化代码。
    - govet              # Vet语法检查 检查 Go 源代码并报告可疑构造,例如参数与格式字符串不一致的 Printf 调用。
    - lll                # Reports long lines.行长度限制规则
    - rowserrcheck       # 检查是否成功检查了行的 Rows.Err。
    - errcheck           # 错误检查, Errcheck 是一个用于检查 Go 代码中未检查错误的程序。在某些情况下,这些未经检查的错误可能是严重错误。
    # - unused             # 检查 Go 代码中是否存在未使用的常量、变量、函数和类型。
    - sqlclosecheck      # 检查那些 sql.Rows, sql.Stmt, sqlx.NamedStmt, pgx.Query 是否关闭
    - gocritic           # 提供诊断功能,用于检查错误、性能和样式问题。无需通过动态规则重新编译即可扩展。动态规则是以声明方式编写的,包括 AST 模式、筛选器、报告消息和可选建议。
    # - bodyclose 检查HTTP响应正文是否关闭成功。. https://github.com/timakin/bodyclose/issues 问题太多了,屏蔽都屏蔽不过来,显式不使用它
#
#  # 启用所有可用的 linter。
#  # Default: false
#  # enable-all: true
#  # 禁用特定 linter
#  # 默认禁用特定 linters: https://golangci-lint.run/usage/linters/#disabled-by-default
#  disable:
#    - asasalint
#  # 启用预设。
#  # https://golangci-lint.run/usage/linters
#  # Default: []
#  presets:
#    - bugs
#
#  # Run only fast linters from enabled linters set (first run won't be fast)
#  # Default: false
#  fast: true

# 4. 检测器 linters 的所有详细配置.
linters-settings:
  # 用于检测长函数的工具。
  funlen:
    # 检查函数中的行数,单个函数的最大行数限制。如果低于 0,则禁用检查。
    # Default: 60
    lines: 250

    # 函数中的语句数。单个函数的最大语句数限制.如果低于 0,则禁用检查。
    # Default: 40
    statements: 200

    # 计算行数时忽略注释。
    # Default false
    # ignore-comments: true

  # 查找可由常量替换的重复字符串。
  goconst:
    # 字符串常量的最小长度。
    # Default: 3
    min-len: 2
    # 最小重复字符出现次数 触发问题的常量字符串计数的最小出现次数。
    # Default: 3
    min-occurrences: 5

    # 忽略测试文件。
    # Default: false
    # ignore-tests: true

    # 查找与值匹配的现有常量。
    # Default: true
    #  match-constant: false


    # 还要搜索重复的数字。
    # Default: false
    # numbers: true


    # 最小值,仅适用于 goconst.numbers
    # Default: 3
    # min: 2

    # 最大值,仅适用于 goconst.numbers
    # Default: 3
    # max: 2


    # 当常量未用作函数参数时忽略。
    # Default: true
    # ignore-calls: false


    # 排除与给定正则表达式匹配的字符串。
    # Default: ""
    # ignore-strings: 'foo.+'

  # 计算和检查函数的圈复杂度
  gocyclo:
    # 报告的代码圈复杂度最低值
    # 默认值:30(但我们建议 10-20)
    min-complexity: 30
#    min-complexity: 20000

  # 对 imports 进行格式化排序
  goimports:
    # A comma-separated list of prefixes, which, if set, checks import paths  with the given prefixes are grouped after 3rd-party packages.
    # Default: ""
    # local-prefixes: github.com/org/project

  # 代码修复;快速、可配置、可扩展、灵活且美观的 Go linter。golint 的直接替代品。
  revive:
    # 同时打开的最大文件数。See https://github.com/mgechev/revive#command-line-flags
    # Defaults to unlimited.
    # max-open-files: 2048

    # 设置为 false 时,忽略带有“GENERATED”标头的文件,类似于 golint。See https://github.com/mgechev/revive#available-rules for details.
    # Default: false
#    ignore-generated-header: true

    # 设置默认严重性。  See https://github.com/mgechev/revive#configuration
    # Default: warning
    # severity: error

    # 启用所有可用规则
    # Default: false
    # enable-all-rules: true

    # 设置默认故障置信度。这意味着置信度小于 0.8 的 linting 错误将被忽略。
    # Default: 0.8
    confidence: 0.2

    rules:  # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md#add-constant
      - name: var-declaration
#      - name: package-comments # 包必须有注释 TODO
      - name: dot-imports
      - name: blank-imports
#      - name: exported # 导出的 方法 必须有注释 TODO
#      - name: var-naming 命名规范
      - name: indent-error-flow
      - name: range
      - name: errorf
      - name: error-naming
      - name: error-strings
      - name: receiver-naming
      - name: increment-decrement
      - name: error-return
      #- name: unexported-return
      - name: time-naming
      - name: context-keys-type
      - name: context-as-argument
  # Vet语法检查 检查 Go 源代码并报告可疑构造,例如参数与格式字符串不一致的 Printf 调用。
  govet:
    # 检查 shadowed variables.
    # Default: false
    check-shadowing: true

    ## 每个分析器的设置
    #settings:
    #  # Analyzer name, run `go tool vet help` to see all analyzers.
    #  printf:
    #    # Comma-separated list of print function names to check (in addition to default, see `go tool vet help printf`).
    #    # Default: []
    #    funcs:
    #      - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
    #      - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
    #      - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
    #      - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
    #  shadow:
    #    # Whether to be strict about shadowing; can be noisy.
    #    # Default: false
    #    strict: true
    #  unusedresult:
    #    # Comma-separated list of functions whose results must be used
    #    # (in addition to default:
    #    #   context.WithCancel, context.WithDeadline, context.WithTimeout, context.WithValue, errors.New, fmt.Errorf,
    #    #   fmt.Sprint, fmt.Sprintf, sort.Reverse
    #    # ).
    #    # Default: []
    #    funcs:
    #      - pkg.MyFunc
    #    # Comma-separated list of names of methods of type func() string whose results must be used
    #    # (in addition to default Error,String)
    #    # Default: []
    #    stringmethods:
    #      - MyMethod
#
    ## 禁用所有分析器
    ## Default: false
    #disable-all: true
    ## Enable analyzers by name.
    ## (in addition to default:
    ##   appends, asmdecl, assign, atomic, bools, buildtag, cgocall, composites, copylocks, defers, directive, errorsas,
    ##   framepointer, httpresponse, ifaceassert, loopclosure, lostcancel, nilfunc, printf, shift, sigchanyzer, slog,
    ##   stdmethods, stringintconv, structtag, testinggoroutine, tests, timeformat, unmarshal, unreachable, unsafeptr,
    ##   unusedresult
    ## ).
    ## Run `go tool vet help` to see all analyzers.
    ## Default: []
    #enable:
    #  - appends
#
    ## 启用所有分析器
    ## Default: false
    ## enable-all: true
#
    ## Disable analyzers by name.
    ## (in addition to default
    ##   atomicalign, deepequalerrors, fieldalignment, findcall, nilness, reflectvaluecompare, shadow, sortslice,
    ##   timeformat, unusedwrite
    ## ).
    ## Run `go tool vet help` to see all analyzers.
    ## Default: []
    #disable:
    #  - appends

  # Reports long lines.行长度限制规则
  lll:
    # 最大行长,将被报告
    # 默认情况下,“\t”计为 1 个字符,可以使用制表符宽度选项 tab-width 进行更改。
    # Default: 120.
#    line-length: 1000 TOTO
    line-length: 100000

    # 空格中的制表符宽度
    # Default: 1
    # tab-width: 1

  # 错误检查, Errcheck 是一个用于检查 Go 代码中未检查错误的程序。在某些情况下,这些未经检查的错误可能是严重错误。
  errcheck:
    # 关于不检查类型断言中的错误的报告:“a := b.(MyStruct)”。
    # 默认情况下,不会报告此类情况。
    # Default: false
    check-type-assertions: false

    # 报告将错误分配给空白标识符:'num, _ := strconv.Atoi(numStr)'。
    # 默认情况下,不会报告此类情况。
    # Default: false
    check-blank: true

    # 已弃用逗号分隔的 pkg:regex 形式的对列表
    # 正则表达式用于忽略 PKG. 中的名称(默认为“fmt:.”)。
    # see https://github.com/kisielk/errcheck#the-deprecated-method for details
    # ignore: fmt:.*,io/ioutil:^Read.*

    # 禁用 errcheck 内置排除列表。
    # See `-excludeonly` option in https://github.com/kisielk/errcheck#excluding-functions for details.
    # Default: false
    # disable-default-exclusions: true

    # DEPRECATED use exclude-functions instead.
    # Path to a file containing a list of functions to exclude from checking.
    # See https://github.com/kisielk/errcheck#excluding-functions for details.
    # exclude: /path/to/file.txt

    # 要从检查中排除的函数列表,其中每个条目都是要排除的单个函数。
    # See https://github.com/kisielk/errcheck#excluding-functions for details.
    # exclude-functions:
      # - io/ioutil.ReadFile
      # - io.Copy(*bytes.Buffer)
      # - io.Copy(os.Stdout)

    # 提供诊断功能,用于检查错误、性能和样式问题。无需通过动态规则重新编译即可扩展。动态规则是以声明方式编写的,包括 AST 模式、筛选器、报告消息和可选建议。
  gocritic:
    enabled-checks:
      - nestingReduce
      - commentFormatting
    settings:
      nestingReduce:
        bodyWidth: 5


# 5. 检测器输出报告配置,比如忽略某些检测器的输出
issues:
  exclude-use-default: true

  # The list of ids of default excludes to include or disable. By default it's empty.
  # 下面的规则,golangci-lint认为应该屏蔽,但是我们选择不屏蔽。所以,`exclude-use-default: true`屏蔽一部分,把下面的再捞出来。
  # golanglint-ci维护的忽略列表里有一些是我们不想屏蔽的,捞出来。这里说一下,使用白名单是好于黑名单的。名单随着golanglint-ci引入更多工具,我们跟进享受好处。我们搞黑名单,就变成自己维护,不如golanglint-ci去维护,更好。
  include:
    - EXC0004 # govet (possible misuse of unsafe.Pointer|should have signature)
    - EXC0005 # staticcheck ineffective break statement. Did you mean to break out of the outer loop
    - EXC0012 # revive exported (method|function|type|const) (.+) should have comment or be unexported
    - EXC0013 # revive package comment should be of the form "(.+)...
    - EXC0014 # revive comment on exported (.+) should be of the form "(.+)..."
    - EXC0015 # revive should have a package comment, unless it's in another file for this package

  exclude-rules:
    - path: _test\.go
      linters:
        - funlen # 规范说单测函数,单个函数可以到160行,但是工具不好做区分处理,这里就直接不检查单测的函数长度
    - linters:
        - staticcheck
      text: "SA6002: argument should be pointer-like to avoid allocations" # sync.pool.Put(buf), slice `var buf []byte` will tiger this
    - linters:
        - lll
      source: "^//go:generate " # Exclude lll issues for long lines with go:generate

  max-same-issues: 0
  new: false
  max-issues-per-linter: 0

# 6. 检测器的敏感度配置
severity:
  # 设置问题的默认严重性。如果定义了严重性规则,但问题不匹配,或者没有为规则提供严重性,这将是应用的默认严重性。

  # 严重性应与所选输出格式支持的严重性名称匹配。
  # - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity
  # - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#SeverityLevel
  # - GitHub: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
  # - TeamCity: https://www.jetbrains.com/help/teamcity/service-messages.html#Inspection+Instance
  #
  # Default: ""
  default-severity: error

  # 如果设置为 true,则“severity-rules”正则表达式将区分大小写。
  # Default: false
  case-sensitive: true

#service:
#  golangci-lint-version: 1.28.x

然后在项目根目录.golangci.yml 文件所在的目录,执行:

golangci-lint run

即可进行检测,有代码问题会输出结果

以上是简单的手动执行 golangci-lint,但是在实际开发中,我们一般会集成到一些开发工具中,让 lint 更加高效有用:

  • 集成到 git:每次 git 提交都需要进行 lint,确保代码质量符合规范

  • 集成到 CI/CD:通过流水线执行 lint,确保代码合规才能 merge 到 master 生产环境

    • gitlab CI/CD
    • Github Action
    • Jenkins
    • ….

三、接入 git

通过 git hook: pre-commit 钩子实现代码提交自动运行 golangci-lint

.git/hooks/pre-commit 创建文件(或修改现有文件)

#!/bin/bash
set -x  # 开启命令追踪
if ! command -v golangci-lint &> /dev/null; then
    echo "golangci-lint not install"
    go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2
fi
cd $(dirname $0)/..
golangci-lint run
if [ $? -ne 0 ]; then
  echo "Linting failed - please fix the issues before committing"
  exit 1
fi

这样子每次提交 commit 就会自动运行 lint,如果出现错误,提交将会失败,

可搜索 git hook 教程学习,然后进行实际配置

四、接入 Github Action

集成到 Github Action 中,提交代码,执行流水线自动执行 golangci-lint

.github/workflows/lint.yml 中:

name: Lint

on: [push, pull_request]

jobs:
  golangci:
    name: lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/set

注意:这只是个案例模版,实际配置请学习官方 Github Action yaml 配置说明

五、接入 gitlab CI/CD

集成到 gitlab CI/CD

在项目根目录创建 .gitlab-ci.yml 文件:

stages:
  - lint     # 代码静态检查
  - test     # 单元测试
  - build    # 构建二进制文件
  - deploy   # 部署阶段

variables:
  GO_VERSION: "1.21"
  BINARY_NAME: "myapp"
  ARTIFACTS_DIR: "artifacts"

# 所有作业都会继承的基础配置
.default_job: &default_job
  image: golang:${GO_VERSION}
  before_script:
    - mkdir -p ${ARTIFACTS_DIR}
    - go version
    
golangci-lint:
  <<: *default_job
  stage: lint
  script:
    # 安装 golangci-lint
    - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2
    # 运行检查
    - $(go env GOPATH)/bin/golangci-lint run ./...
  artifacts:
    when: on_failure
    paths:
      - ${ARTIFACTS_DIR}/lint-report.txt
      
.....

注意:这只是个案例模版,实际配置请学习官方 GitLab yaml 配置说明

六、接入 Idea Goland

和 go fmt 一样,在开发工具中配置 golangci-lint 工具,每次代码保存自动进行 lint 检查

不是很推荐,会影响写代码的速度……….

配置方式和 go imports 的配置方式几乎一致,参考文章:Goland 开发工具一些关键配置

总结

接入 DevOps 、开发工具的逻辑非常简单,无非就是在执行流水线前多执行一个步骤:下载安装 golangci-lint,执行 golangci-lint run

几乎大同小异