Если вы видите что-то необычное, просто сообщите мне. Skip to main content

Инструкция для линтинга 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Проврка GoGO codeкода 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в программе на Go programs.

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-lint projectбыл 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

golangci-lint linters The vast majority of available linters are disabled by default

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 golangci-lint provides a nice output with colors, source code lines and marked identifiers

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-lint forдля 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-lint websiteсо 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 run onдля 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"
  ]
}

golangCI-Lint in action in Visual Studio Code 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.

golangCI-Lint in action in Neovim voila

Refer to the golangci-lint docs for more information on how to integrate it with other editors.

Setting up a pre-commit hook

Pre-commit comic strip Image source

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.

golangci-lint running via Git pre-commit hook

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.

golangci-lint inline displays 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!