Introduction to Linting in Go

07 Jul 2021
Introduction to Linting in Go

In this blog I’ll be explaining details about linting and it’s benefits. You’ll also learn about multiple linters available in Golang and how you can use start using linting in your Go applications.

What is Linting?

When you are building any software application you’ll have to deal with a lot of bugs and errors during development. No matter how good you are, there will always be a fair chance that you miss something. Linting is one way to avoid such errors right during developement.

Programming bugs meme

Linting is the process of analyzing source code for bugs, programming and stylistic errors. It also helps in following a good coding standard across the project where multiple developers might be working together. Linting is achieved using a tool popularly known as linter.

Importance of linting code

  1. Detecting bugs, typos, unused code and possible errors.
  2. Linting helps in faster development.
  3. Improving the overall quality of the code. Eg. Setting line length limit, avoiding redundant print statements, etc.
  4. Faster and better code review since linting already performs static code checks.

Linting in Golang

Now that we have a basic understanding about linting, let’s learn how to use this in our Go applications. Actually golang already provides some basic tools like gofmt that deals with formatting the Go code and govet that examines the source code and reports suspicious constructs.

There is a wide range of linters available today most of which have been built by the amazing Go community. Let’s discuss about some some useful linters -

  • goimports - handles formatting and automatically adds the required packages. It also removes the unreferenced imports.
  • gocyclo - calculates cyclomatic complexities of functions in Go source code. This is really helpful in identifying possible refactoring to make functions concise and clean.
  • unused - checks for unused constants, variables, functions and types.
  • errcheck - checks for unhandled errors in go programs. This helps in identifying possible critical bugs that were missed during developement.

There are 50+ linters available. You don’t want to import each one of them in your package manually. To solve this we have golangci-lint. It is the preferred Go linters aggregator that covers most the linters you’ll ever need. It runs these linters in parallel and reuses the build cache for improved performance during development.

Install golangci-lint

We will install golangci-lint by fetching the binary via curl. You can modify the version if required. Here we are installing the v1.41.1 version.

# 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 v1.41.1

Let’s confirm the installation by running the following command.

golangci-lint --version

Running golangci-lint

To run the linters run the following command.

# for current directory and subdirectories
golangci-lint run

# for a specific directory
golangci-lint run <dirname>

This tool comes with a set of linters that are enabled by default while others are disabled. You can check all the available linters using help

golangci-lint help linters

This would return a list of linters. Output would be like this.

Enabled by default linters:
deadcode: Finds unused code [fast: true, 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: true, auto-fix: false]
gosimple (megacheck): Linter for Go source code that specializes in simplifying a code [fast: true, 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: true, auto-fix: false]
ineffassign: Detects when assignments to existing variables are not used [fast: true, auto-fix: false]
staticcheck (megacheck): Staticcheck is a go vet on steroids, applying a ton of static analysis checks [fast: true, auto-fix: false]
structcheck: Finds unused struct fields [fast: true, auto-fix: false]
typecheck: Like the front-end of a Go compiler, parses and type-checks Go code [fast: true, 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: true, auto-fix: false]

If you want to enable a specific linter use -E/--enable option. Similarly to disable a linter use -D/--disable option with run command.

golangci-lint run -E errcheck

You can disable all linters using --disable-all option.

golangci-lint run --disable-all

Using golangci-lint

You can possibly add tens of linters in the above command and enable them, but this seems like a lot of work. To make things clean and simple - recommended way to use golangci-lint is via configuration file.

Create the .golangci-lint.yml in root directory of your project. We are using the yaml format but you can choose toml or json format as well.

You can directly use this official sample configuration that contains a lot of linters with explanation. Feel free to edit this and add new linters available here.

Here is the golanci-lint.yml I use for my projects.

run:
   concurrency: 4
   timeout: 3m
   issues-exit-code: 1
   tests: true
   skip-dirs-use-default: true
   modules-download-mode: readonly

 output:
   format: colored-line-number
   print-issued-lines: true
   print-linter-name: true
   uniq-by-line: true

 linters-settings:
   errcheck:
     check-type-assertions: true
     check-blank: true
   govet:
     check-shadowing: true
     enable-all: true
     disable-all: false
     disable:
       - fieldalignment
   unused:
     check-exported: true
   gocyclo:
     min-complexity: 7
   gocognit:
     min-complexity: 7
   nakedret:
     max-func-lines: 10
   goconst:
     min-len: 3
     min-occurrences: 2
   gofmt:
     simplify: true
   maligned:
     suggest-new: true
   fieldalignment:
     suggest-new: true
   tagliatelle:
     case:
       rules:
         json: snake
   lll:
     line-length: 160
   revive:
     ignore-generated-header: true
     severity: warning
     formatter: friendly
     confidence: 0.8
     errorCode: 0
     warningCode: 0
     rules:
       - name: atomic
       - name: blank-imports
       - name: context-as-argument
       - name: context-keys-type
       - name: dot-imports
       - name: error-return
       - name: error-strings
       - name: error-naming
       - name: exported
       - name: if-return
       - name: increment-decrement
       - name: var-naming
       - name: var-declaration
       - name: package-comments
       - name: range
       - name: receiver-naming
       - name: time-naming
       - name: unexported-return
       - name: indent-error-flow
       - name: errorf
       - name: empty-block
       - name: superfluous-else
       - name: unused-parameter
       - name: unreachable-code
       - name: redefines-builtin-id

 linters:
   enable:
     - bodyclose
     - deadcode
     - depguard
     - dogsled
     - dupl
     - errcheck
     - exportloopref
     - exhaustive
     - funlen
     - gochecknoinits
     - goconst
     - gocritic
     - gocyclo
     - gofmt
     - goimports
     - revive
     - gomnd
     - goprintffuncname
     - gosimple
     - govet
     - ineffassign
     - lll
     - misspell
     - nakedret
     - noctx
     - nolintlint
     - rowserrcheck
     - staticcheck
     - structcheck
     - stylecheck
     - unconvert
     - unparam
     - unused
     - varcheck
     - whitespace
   fast: false

 issues:
   exclude-use-default: true
   max-issues-per-linter: 0
   max-same-issues: 0
   new-from-rev: origin/master
   exclude-rules:
     - path: test
       linters:
         - lll
         - dupl
         - funlen
         - exhaustivestruct
         - gocognit
         - fieldalignment
         - scopelint
         - testpackage
         - paralleltest

That’s it. Run golangci-lint run from root folder of your project. A good idea is to add this as a part of your CI pipeline. You can use the golangci github action for projects hosted on github.


Excluding linting in Go code

golangci-lint provides a //nolint directive that can be used in code to ignore linting for a particular line/block or file.

// exclude a line
// here we are ignoring this line for golint linter
var userLastName string //nolint:golint

// Here we are excluding this function block for all linters.
//nolint
function solve() {
    ...
}

// to exclude the complete file use nolint on top of file
//nolint
pkg myservice

Congrats! You are now ready to use linting to your Go projects. This will help you making your code less error-prone and improve overall code quality.

Resources

Books to learn Golang


I hope you learned something new. Feel free to suggest improvements ✔️

I share regular updates and resources on Twitter. Let’s connect!

Keep exploring 🔎 Keep learning 🚀

Liked the content? Do support :)

Paypal - Mohit Khare
Buy me a coffee