Инструкция для линтинга Go программ
LintingЛинтинг isэто theпроцесс processобнаружения ofи identifyingопвещения andразличных reportingшаблонов onнайденных patternsв foundкоде, inс code,целью withулучшения theсостояния, aimи ofотлавливания improvingбагов consistency,на andранних catchingстадиях bugsразработки. earlyЭто inобычно theполезно developmentпри cycle.работе Thisв isкомане, particularlyтак usefulкак whenпомогает workingделать inвесь aкод teamодинаковым asне itзависимо helpsот toтого makeкто allименно codeпишет, lookубирать theизлишнюю sameсложность, noи matterделать who’sкод writingлегче it,в whichобслуживании. reducesВ complexity,этой andстатье, makesя theрасскажу codeсо easierвсех toсторон maintain.о Inнастройке thisлинтинга article, I’ll demonstrate a comprehensive linting setup forдля Go programs,програм, andи talkпоговорим aboutо theлучших bestспособах wayвведения toих introduceв it into an existing project.эксплуатацию.
LintingЛинтинг codeкода isодин oneиз ofсамых theпростых mostвещей, basicкоторые thingsвы youможете canделать doчтобы toубедиться ensureв consistentсодержании codingкодовых practicesпрактик inв project.проекте. Go alreadyуже venturesзаходи fartherдальше thanдругих mostязыков otherпрограммирования programmingиспользуя languagesgofmt
, byинструмент bundlingформатирования gofmt,которые aпроверяет, formattingчто toolвесь that ensures that all Go code looks the same, but it only deals with how code is being formatted. Theкод go выглядит одинаково, но правда работает только с тем, как код выглядит. Инструмент vet
toolgo isязыка alsoтак availableже toможет helpпомочь withс detectingопределением suspiciousстранных constructsконструкций, thatкоторые mayмогут notбыть beпойманы caughtкомпилятором, byно theон compile,отлавливает butтолько itограниченное onlyколичество catchesпотенциальных a limited amount of potential issues.проблем.
TheЗадача taskразработки ofболее developingвсесторнних moreлинтинг comprehensiveинструментов lintingбыла toolsоставлена hasна beenширокую leftобщественность, toи theобщество widerуже community,принесло andгору thisлинтеров, hasкаждый yieldedдля aсвоей mountainцели. ofИзветсные linters,примеры eachвключают oneв with a specific purpose. Prominent examples include:себя:
- unused -
ChecksПровркаGoGOcodeкодаforнаunusedнеиспользуемыеconstants,констатны,variables,переменные,functionsфункцииandиtypes.типы. - goconst -
FindНахождениеrepeatedповторяющихсяstringsстрок,thatкоторыеcouldмогутbeбытьreplacedзамененыby a constant.константой. - gocyclo -
ComputesВычисленияandиchecksпроверкиtheцикличнойcyclomaticсложностиcomplexity of functions.функций. - errcheck -
DetectОбнаружениеuncheckedнепроверяемыхerrorsошибокinв программе на Goprograms.
TheПроблема problemв withналичии havingтакого soбольшго manyнабора standaloneотдельных lintingинструментов, toolsв isтом, thatчто youвам haveнужно toкачать downloadкаждую eachпо individualотдельности linterи yourselfуправлять andих manageверсиями. theirВ versions.добавок, Additionally,запуск runningкаждой eachиз oneних ofпо themочереди inможет sequenceбыть mayочень be too slow. Due to these reasons,медленно. golangci-lint сборщик, aкоторый запускает линтеры в паралели, переиспользует Go lintersкэш, aggregatorи thatхранит runsрезультат lintersанализа inдля parallel,того, reusesчтобы theулучшить производительность паралельного запуска, являетя одним из предпочитаемым способом для настройки линтера в Go build cache, and caches analysis results for much improved performance on subsequent runs, is the preferred way to setup linting in Go projects.проекте.
TheПроект golangci-golang-lintprojectбыл wasразработан developedдля toсбора aggregateи andзапуска runнескольких severalотдельных individualлинтеров lintersв inпаралели, parallelдля forудобствао convenienceи andпроизводительности. performanceКогда reasons.вы Whenставите youпрограмму, installвы theполучаете program, you’ll get aboutпорядка 48 lintersлинтеров includedвключительно(во (atвремя the time of writing)написания), andи youвы canможете proceedпродолжит toвыбирать pickкакой andиз chooseних whichважный onesдля areвашего importantпроекта. forКроме yourтого project.во Asideвремя fromих runningлокального itзапуска, locallyесть duringвозможность development,настроих youих areв ableкачестве toшага set it up as part of your continuous integration workflow.CI.
InstallingУстановка golangci-golang-lint
UseКоманда theниже commandиспользуется belowдля toустаовки installgolang-lint
golangci-lintлокально locallyна onлюбую anyоперационную operating system. Other OS-specific installation options can be found here.систему.
$ go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
OnceПосле installed,установки, youвы shouldдолжны checkпроверить theверсию version that was installed:пакета:
$ golangci-lint version
golangci-lint has version v1.40.1 built from (unknown, mod sum: "h1:pBrCqt9BgI9LfGCTKRTSe1DfMjR6BkOPERPaXJYXA6Q=") on (unknown)
YouВы canможете alsoтак viewже theпосмотреть allвсе theдоступные availableлинтер lintersследующей through the following command:командой:
$ golangci-lint help linters
IfЕсли youвы runзапустили theвключенные enabledлинтеры lintersв atкорне theпроекта, rootвы ofможете yourувидеть projectошибки. directory,Каждая youпроблема mayсодержит seeв someсебе errors.описание Eachтого, problemчто isименно reportedнужно withисправить, allи theсодержит contextкраткое youописание needпроблемы, toа fixтак itже includingфайл aи shortстроку descriptionс of the issue, and the file and line number where it occurred.проблемой.
$ golangci-lint run # equivalent of golangci-lint run ./...
golangci-lint run may spot some problems with the default settings
YouВы canможете alsoвыбрать, chooseкакую whichпапку directoriesи andфайлы filesнеобоходимо toпроанализировать analyseуказав byв passing one or more directories or paths to files.команде:
$ golangci-lint run dir1 dir2 dir3/main.go
ConfiguringНастройка golangci-lint
GolangCI-Lint isразработан designedбыть toгибким beнасколько asэто flexibleвозможно, asдля possibleразличного forнабора aслучаев. wideНастройка rangegolang-lint
ofможет useуправляться cases.через Theпараметры configurationкоммандной forстроки golangci-lintили canфайл beнастройки, managedтак throughже commandимеет lineпреимущства optionsстарого orнад aновым configurationесли file,один althoughпараметр theзадается formerнесколько hasраз. aВот greaterпример priorityкоторый overиспользует theпараметры latterкомандной ifстроки bothдля areотключения usedвсех atлинтеров theи sameуказания time.определенных Here’sлинтеров, anкоторые exampleдолжны that uses command-line options to disable all linters and configure the specific linters that should be run:запуститься.
$ golangci-lint run --disable-all -E revive -E errcheck -E nilerr -E gosec
YouВы canможете alsoтак runже theзапустить presetsс providedнастройками byпо-умолчанию golangci-lint.из Here’sgolang-lint
. howВот toкак findнайти outинформацию aboutо theдоступных available presets:настройках.
$ 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
ThenТеперь youможно canзапустить runнастройки aпо-умолчанию presetпередав byих passingимена itsв nameключе to the --preset
orили -p
: flag:
$ golangci-lint run -p bugs -p error
ConfiguringНастройку golangci-golang-lintforдля aпроекта projectлучше isпроизводить bestчерез doneфайл. throughТаким aобразом, configurationвы file.сможете Thatнастравить way,отдельные you’llнастройки beлинтера, ableкоторые toне configureдоступны specificиз linterкомандной optionsстроки. whichВы isможете notуказать possibleyaml, viatoml command-lineили options.json Youформат mayфайла specifyнастройки, theно configurationя fileрекомендую inостановиться YAML,на TOMLyaml orформате, JSONтак format,сказано butв Iоф recommend sticking with the YAML format (документации.golangci.yml or .golangci.yaml) since that’s what is used on the official documentation pages.
GenerallyГоворя speaking,в youобщем, shouldвы createдолжны project-specificсоздать configurationотдельную inнастроку theдля rootпроекта ofв your project directory. The program will automatically look for them in the directory of the file to be linted, and in successive parent directories all the way up to the root directory of the filesystem. This means you can achieve a global configuration for all projects by placing a config file in your home directory (not recommended)корне. ThisПрограмма fileавтоматически willнайдет beего usedв ifпапке aи locallyпойдет scopedвыше configдо fileкорня doesпроекта. notЭто exist.значит вы можете достигнуть глобальной настройки для всех проектов поместив файл с настройками в домашней директории(не советую). Этот файл будет использован если локальные настройки не будут найдены.
AПростой sampleфайл configuration fileнастройки isдоступен availableна onвеб theсайте golangci-golang-lintwebsiteсо with allвсеми supportedподдерживаемыми optionsопциями, theirих description,описание, andи defaultстандартные value.значения. YouВы canможете useиспользовать thatих asкак aостчетную startingточку pointпри whenсоздании creatingсвоей yourнастройки. ownИмейте configuration.в Keepвиду, inчто mindнекоторые thatлинтеры someвыполняют lintersпохожие performфункции, similarпоэтому functionsнужно soвключать youлинтеры needобдуманно, toчтобы enableизбежать lintersизбыточных deliberatelyзаписей. to avoid redundant entries. Here’s theВот generalобщая configurationнастройка thatкоторую Iможно useиспользовать forдля my personal projects:проекта:
.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
SuppressingРешение lintingошибок errorsлинтера
It’sИногда sometimesнеобхоидмо necessaryвыключить toопределенные disableпроблемы specificлинтера, lintingво issuesвремя thatработы cropс upкодом. inЭто aможно fileполучить orдвумя package.способами: Thisчерез mayкоманду benolint
achievedи inчерез twoправила mainисключения ways:в throughфайле theнастройки. nolintДавайте directive,посмотрим andкаждый throughподход exclusionпо rules in the configuration file. Let’s take a look at each approach in turn.очереди.
theкоманда nolint directive
Let’sПредположим assumeу weнас haveесть theследующий followingкод, codeкоторый thatвыводит printsпреводслучайное aцелое pseudoчисло randomв integerстандартный to the standard output:вывод.
main.go
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(rand.Int())
}
ExecutingВыполнив golangci-golang-lint runonдля thisэтого fileфайла willмы produceполучим theследущий followingнабор errorошибок, providedтак thatкак theвключен gosec
linter is enabled:линтер:
$ 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())
^
TheЛинтер linterпоощряет isиспольлзвание encouragingметода theInt
useиз ofcrypto-rand
theвзамет, Intиз-за methodкриптографической fromбезопасности. crypto/randно insteadон becauseимеет itбескомпромисно isменее cryptographically more secure, but it has the tradeoff of a less friendlyдружелюбный API andи slowerмедленную performance.производительность. IfЕсли you’reвас OKэто withне theбеспокоит, tradeoffвы ofможете lessпросто secureпроигнорировать pseudoошибку randomдобавив numbersкоманду fornolint
fasterв speeds,нужный you can ignore the error by adding a nolint directive on the necessary line:файл:
func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(rand.Int()) //nolint
}
ByСогласно Goдоговоренности, convention,комментарии machine-readableдля commentsмашины shouldне haveдолжны noсодедржать spaces,пробела. soпоэтому useнужно использовать //nolint instead ofвместо // nolint.
This inline usage ofИспользование nolint
causesприведет allк theтому, lintingчто issuesэта detectedнайденная forпроблема thatбудет line to be disabled. You can disable the issues from a specific linter by specifying its name in the directive (recommended)проигнорированна. ThisВы allowsможете issuesвыключить raisedпроблемы onопределенного thatлиинтера lineуказав byеё otherимя lintersв toкомманде. comeЭто through.позволит пропустить строку конкретному линтеру, но остальные пройдутся по ней.
main.go
func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(rand.Int()) //nolint:gosec
}
WhenКогда youвы use aиспользуете nolint
directiveкоманду atв theначале topфайла, ofто aон file,выключает itлинтер disablesдля allвсего the linting issues for that file:файла:
main.go
//nolint:govet,errcheck
package main
YouВы canможете alsoтак excludeже issuesотключить forлинтер aдля blockблока ofкода(например code (such as a function)функции), by using aиспользуя nolint
directiveкоманду atв theначале beginningблока of the block.кода.
main.go
//nolint
func aFunc() {
}
AfterПосле adding aдобавления nolint
directive,команды, itрекомендуем, isдобавить recommendedкомментарий thatкоторый youобъясняяет addзачем aэто commentнужно. explainingЭтот whyкомементарий itдолжен isбыть needed.помещен Thisна commentстроку shouldс be placed on the same line as the flag itself:флагом:
main.go
func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(rand.Int()) //nolint:gosec // for faster performance
}
YouВы canможете enforceуказать theправила conventionsдля thatвашей yourкоманды team should follow regardingотносительно nolint
commentsкомментирования by enabling theвключив nolintlint
linter.линтер. ItОн canможет reportоповещять issuesпроблемы regardingотносительно the use ofпроблем nolint
withoutбез namingуказания theопределенных specificотключенных linterлинтеров, beingили suppressed,без orтребования without a comment explaining why it was needed.комментария.
$ golangci-lint run
main.go:11:26: directive `//nolint` should mention specific linter such as `//nolint:my-linter` (nolintlint)
fmt.Println(rand.Int()) //nolint
^
exclusionПравила rulesисключения
ExclusionПравила rulesисключения canмогут beбыть specifiedуказаны inфайле theнастроек configurationдля fileболее forдетального a more granular control on what files are linted, and what issues are reported. For example, you can disable certain linters from running on test files (_test.go)контроля, orкакие youфайлы canдолжны disableбыть aзалинтены, linterи fromо producingкакой certainпроблеме errorsнужно project-wide:сообщать. Для примера, вы можете выключить определенный линтер из запуска файлов тестов(_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"
IntegrationИнтеграция withв existingсуществующий projectsпроект
При добавлении 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!