Инструкция для линтинга Go программ
Линтинг это процесс обнаружения и опвещения различных шаблонов найденных в коде, с целью улучшения состояния, и отлавливания багов на ранних стадиях разработки. Это обычно полезно при работе в комане, так как помогает делать весь код одинаковым не зависимо от того кто именно пишет, убирать излишнюю сложность, и делать код легче в обслуживании. В этой статье, я расскажу со всех сторон о настройке линтинга для Go програм, и поговорим о лучших способах введения их в эксплуатацию.
Линтинг кода один из самых простых вещей, которые вы можете делать чтобы убедиться в содержании кодовых практик в проекте. Go уже заходи дальше других языков программирования используя gofmt
, инструмент форматирования которые проверяет, что весь код go выглядит одинаково, но правда работает только с тем, как код выглядит. Инструмент vet
go языка так же может помочь с определением странных конструкций, которые могут быть пойманы компилятором, но он отлавливает только ограниченное количество потенциальных проблем.
Задача разработки более всесторнних линтинг инструментов была оставлена на широкую общественность, и общество уже принесло гору линтеров, каждый для своей цели. Изветсные примеры включают в себя:
- unused - Проврка GO кода на неиспользуемые констатны, переменные, функции и типы.
- goconst - Нахождение повторяющихся строк, которые могут быть заменены константой.
- gocyclo - Вычисления и проверки цикличной сложности функций.
- errcheck - Обнаружение непроверяемых ошибок в программе на Go
Проблема в наличии такого большго набора отдельных инструментов, в том, что вам нужно качать каждую по отдельности и управлять их версиями. В добавок, запуск каждой из них по очереди может быть очень медленно. golangci-lint сборщик, который запускает линтеры в паралели, переиспользует Go кэш, и хранит результат анализа для того, чтобы улучшить производительность паралельного запуска, являетя одним из предпочитаемым способом для настройки линтера в Go проекте.
Проект golang-lint
был разработан для сбора и запуска нескольких отдельных линтеров в паралели, для удобствао и производительности. Когда вы ставите программу, вы получаете порядка 48 линтеров включительно(во время написания), и вы можете продолжит выбирать какой из них важный для вашего проекта. Кроме того во время их локального запуска, есть возможность настроих их в качестве шага CI.
Установка golang-lint
Команда ниже используется для устаовки golang-lint
локально на любую операционную систему.
$ go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
После установки, вы должны проверить версию пакета:
$ golangci-lint version
golangci-lint has version v1.40.1 built from (unknown, mod sum: "h1:pBrCqt9BgI9LfGCTKRTSe1DfMjR6BkOPERPaXJYXA6Q=") on (unknown)
Вы можете так же посмотреть все доступные линтер следующей командой:
$ golangci-lint help linters
Если вы запустили включенные линтеры в корне проекта, вы можете увидеть ошибки. Каждая проблема содержит в себе описание того, что именно нужно исправить, и содержит краткое описание проблемы, а так же файл и строку с проблемой.
$ golangci-lint run # equivalent of golangci-lint run ./...
Вы можете выбрать, какую папку и файлы необоходимо проанализировать указав в команде:
$ golangci-lint run dir1 dir2 dir3/main.go
Настройка golangci-lint
GolangCI-Lint разработан быть гибким насколько это возможно, для различного набора случаев. Настройка golang-lint
может управляться через параметры коммандной строки или файл настройки, так же имеет преимущства старого над новым если один параметр задается несколько раз. Вот пример который использует параметры командной строки для отключения всех линтеров и указания определенных линтеров, которые должны запуститься.
$ golangci-lint run --disable-all -E revive -E errcheck -E nilerr -E gosec
Вы можете так же запустить с настройками по-умолчанию из golang-lint
. Вот как найти информацию о доступных настройках.
$ golangci-lint help linters | sed -n '/Linters presets:/,$p'
Linters presets:
bugs: asciicheck, bodyclose, durationcheck, errcheck, errorlint, exhaustive, exportloopref, gosec, govet, makezero, nilerr, noctx, rowserrcheck, scopelint, sqlclosecheck, staticcheck, typecheck
comment: godot, godox, misspell
complexity: cyclop, funlen, gocognit, gocyclo, nestif
error: errcheck, errorlint, goerr113, wrapcheck
format: gci, gofmt, gofumpt, goimports
import: depguard, gci, goimports, gomodguard
metalinter: gocritic, govet, revive, staticcheck
module: depguard, gomoddirectives, gomodguard
performance: bodyclose, maligned, noctx, prealloc
sql: rowserrcheck, sqlclosecheck
style: asciicheck, depguard, dogsled, dupl, exhaustivestruct, forbidigo, forcetypeassert, gochecknoglobals, gochecknoinits, goconst, gocritic, godot, godox, goerr113, goheader, golint, gomnd, gomoddirectives, gomodguard, goprintffuncname, gosimple, ifshort, importas, interfacer, lll, makezero, misspell, nakedret, nlreturn, nolintlint, paralleltest, predeclared, promlinter, revive, stylecheck, tagliatelle, testpackage, thelper, tparallel, unconvert, wastedassign, whitespace, wrapcheck, wsl
test: exhaustivestruct, paralleltest, testpackage, tparallel
unused: deadcode, ineffassign, structcheck, unparam, unused, varcheck
Теперь можно запустить настройки по-умолчанию передав их имена в ключе --preset
или -p
:
$ golangci-lint run -p bugs -p error
Настройку golang-lint
для проекта лучше производить через файл. Таким образом, вы сможете настравить отдельные настройки линтера, которые не доступны из командной строки. Вы можете указать yaml, toml или json формат файла настройки, но я рекомендую остановиться на yaml формате, так сказано в оф документации.
Говоря в общем, вы должны создать отдельную настроку для проекта в корне. Программа автоматически найдет его в папке и пойдет выше до корня проекта. Это значит вы можете достигнуть глобальной настройки для всех проектов поместив файл с настройками в домашней директории(не советую). Этот файл будет использован если локальные настройки не будут найдены.
Простой файл настройки доступен на веб сайте golang-lint
со всеми поддерживаемыми опциями, их описание, и стандартные значения. Вы можете использовать их как остчетную точку при создании своей настройки. Имейте в виду, что некоторые линтеры выполняют похожие функции, поэтому нужно включать линтеры обдуманно, чтобы избежать избыточных записей. Вот общая настройка которую можно использовать для проекта:
.golangci.yml
linters-settings:
errcheck:
check-type-assertions: true
goconst:
min-len: 2
min-occurrences: 3
gocritic:
enabled-tags:
- diagnostic
- experimental
- opinionated
- performance
- style
govet:
check-shadowing: true
nolintlint:
require-explanation: true
require-specific: true
linters:
disable-all: true
enable:
- 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
Решение ошибок линтера
Иногда необхоидмо выключить определенные проблемы линтера, во время работы с кодом. Это можно получить двумя способами: через команду nolint
и через правила исключения в файле настройки. Давайте посмотрим каждый подход по очереди.
команда nolint
Предположим у нас есть следующий код, который выводит преводслучайное целое число в стандартный вывод.
main.go
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(rand.Int())
}
Выполнив golang-lint run
для этого файла мы получим следущий набор ошибок, так как включен gosec
линтер:
$ golangci-lint run -E gosec
main.go:11:14: G404: Use of weak random number generator (math/rand instead of crypto/rand) (gosec)
fmt.Println(rand.Int())
^
Линтер поощряет испольлзвание метода Int
из crypto-rand
взамет, из-за криптографической безопасности. но он имеет бескомпромисно менее дружелюбный API и медленную производительность. Если вас это не беспокоит, вы можете просто проигнорировать ошибку добавив команду nolint
в нужный файл:
func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(rand.Int()) //nolint
}
Согласно договоренности, комментарии для машины не должны содедржать пробела. поэтому нужно использовать //nolint вместо // nolint.
Использование nolint
приведет к тому, что эта найденная проблема будет проигнорированна. Вы можете выключить проблемы определенного лиинтера указав её имя в комманде. Это позволит пропустить строку конкретному линтеру, но остальные пройдутся по ней.
main.go
func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(rand.Int()) //nolint:gosec
}
Когда вы используете nolint
команду в начале файла, то он выключает линтер для всего файла:
main.go
//nolint:govet,errcheck
package main
Вы можете так же отключить линтер для блока кода(например функции), используя nolint
команду в начале блока кода.
main.go
//nolint
func aFunc() {
}
После добавления nolint
команды, рекомендуем, добавить комментарий который объясняяет зачем это нужно. Этот комементарий должен быть помещен на строку с флагом:
main.go
func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(rand.Int()) //nolint:gosec // for faster performance
}
Вы можете указать правила для вашей команды относительно nolint
комментирования включив nolintlint
линтер. Он может оповещять проблемы относительно проблем nolint
без указания определенных отключенных линтеров, или без требования комментария.
$ golangci-lint run
main.go:11:26: directive `//nolint` should mention specific linter such as `//nolint:my-linter` (nolintlint)
fmt.Println(rand.Int()) //nolint
^
Правила исключения
Правила исключения могут быть указаны файле настроек для более детального контроля, какие файлы должны быть залинтены, и о какой проблеме нужно сообщать. Для примера, вы можете выключить определенный линтер из запуска файлов тестов(_test.go
) или вы можете выключить линтер для всего проекта
.golangci.yml
issues:
exclude-rules:
- path: _test\.go # disable some linters on test files
linters:
- gocyclo
- gosec
- dupl
# Exclude some gosec messages project-wide
- linters:
- gosec
text: "weak cryptographic primitive"
Интеграция в существующий проект
При добавлении golangcli-lint
в существующий проект, вы можете получить большое количество проблем и может быть трудно исправить все их одновременно. Однако, это не значит, что вы должны бросить идею линтинга для вашего проекта по этой причине.
When adding golangci-lint to an existing project, you may get a lot of issues and it may be difficult to fix all of them at once. However, that doesn’t mean that you should abandon the idea of linting your project for this reason. There is a new-from-rev setting that allows you to show only new issues created after a specific git revision which makes it easy to lint new code only until adequate time can be budgeted to fix older issues. Once you find the revision you want to start linting from (with git log), you can specify it in your configuration file as follows:
.golangci.yml
issues:
# Show only new issues created after git revision: 02270a6
new-from-rev: 02270a6
Integrating golangci-lint in your editor
GolangCI-Lint supports integrations with several editors in order to get quick feedback. In Visual Studio Code, all you need to do is install the Go extension, and add the following lines to your settings.json file:
settings.json
{
"go.lintTool":"golangci-lint",
"go.lintFlags": [
"--fast"
]
}
Vim users can integrate golangci-lint with a variety of plugins including vim-go, ALE, and Syntastic. You can also integrate it with coc.nvim, vim-lsp, or nvim.lspconfig with help of golangci-lint-langserver. Here’s how I integrated golangci-lint in my editor with coc.nvim. First, install the language server:
$ go install github.com/nametake/golangci-lint-langserver@latest
Next, open the coc.nvim
config file with :CocConfig
, and add the following lines:
coc-settings.json
{
"languageserver": {
"golangci-lint-languageserver": {
"command": "golangci-lint-langserver",
"filetypes": ["go"],
"initializationOptions": {
"command": ["golangci-lint", "run", "--out-format", "json"]
}
}
}
}
Save the config file, then restart coc.nvim with :CocRestart, or open a new instance of Vim. It should start working as soon as a Go file is open in the editor.
Refer to the golangci-lint docs for more information on how to integrate it with other editors.
Setting up a pre-commit hook
Running golangci-lint
as part of your Git pre-commit hooks is a great way to ensure that all Go code that is checked into source control is linted properly. If you haven’t set up a pre-commit hook for your project, here’s how to set one up with pre-commit, a language-agnostic tool for managing Git hook scripts.
Install the pre-commit
package manager by following the instructions on this page, then create a .pre-commit-config.yaml
file in the root of your project, and populate it with the following contents:
.pre-commit-config.yaml
repos:
- repo: https://github.com/tekwizely/pre-commit-golang
rev: v0.8.3 # change this to the latest version
hooks:
- id: golangci-lint
This configuration file extends the pre-commit-golang repository which supports various hooks for Go projects. The golangci-lint
hook targets staged files only, which is handy for when introducing golangci-lint
to an existing project so that you don’t get overwhelmed with so many linting issues at once. Once you’ve saved the file, run pre-commit install
to set up the git hook scripts in the current repository.
$ pre-commit install
pre-commit installed at .git/hooks/pre-commit
On subsequent commits, the specified hooks will run on all staged .go files and halt the committing process if errors are discovered. You’ll need to fix all the linting issues before you’ll be allowed to commit. You can also use the pre-commit run command if you want to test the pre-commit hook without making a commit.
Continuous Integration (CI) workflow
Running your project’s linting rules on each pull request prevents code that is not up to standards from slipping through into your codebase. This can also be automated by adding golangci-lint to your Continuous Integration process. If you use GitHub Actions, the official Action should be preferred over a simple binary installation for performance reasons. After setting it up, you’ll get an inline display of any reported issues on pull requests.
During the setup process, ensure to pin the golangci-lint version that is being used so that it yields consistent results with your local environment. The project is being actively developed, so updates may deprecate some linters, or report more errors than previously detected for the same source code.
Conclusion
Linting your programs is a sure fire way to ensure consistent coding practices amongst all contributors to a project. By adopting the tools and processes discussed in this article, you’ll be well on your way to doing just that.
Thanks for reading, and happy coding!