Golangci-lint: Your Go Guardian Against Code Smells

Published: (March 23, 2026 at 12:41 AM EDT)
3 min read
Source: Dev.to

Source: Dev.to

Why Not Just go vet?

go vet catches a narrow set of issues — wrong printf format strings, unreachable code, bad struct tags. It is a baseline, not a linter suite. golangci-lint runs dozens of linters in a single pass and reports unified output. It is fast because it reuses the Go build cache and runs linters concurrently.

Install It

# Homebrew
brew install golangci-lint

# Go install (pinned version)
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

Verify with golangci-lint --version. The v2 config format (version: "2") is current.

A Starter Configuration

Create .golangci.yml at your project root. Start with default: standard and add linters that catch real problems:

version: "2"
linters:
  default: standard
  enable:
    - bodyclose       # unclosed HTTP response bodies
    - contextcheck    # context.Context misuse
    - errname         # error type naming (ErrFoo)
    - errorlint       # unwrapped errors break errors.Is/As
    - exhaustive      # missing switch/map cases
    - gocognit        # cognitive complexity
    - gosec           # security issues
    - nestif          # deeply nested ifs
    - noctx           # HTTP requests without context
    - prealloc        # slice preallocation hints
    - unconvert       # unnecessary type conversions
    - unparam         # unused function parameters
  settings:
    gocognit:
      min-complexity: 20
    errcheck:
      check-type-assertions: true
  exclusions:
    presets:
      - comments
      - common-false-positives
    rules:
      - linters: [funlen, goconst, gosec, noctx]
        path: _test\.go

This gives you meaningful feedback on day one without a wall of noise.

Linters Worth Understanding

gosec

Flags security issues — SQL injection, weak crypto, hardcoded credentials. In a real config you will exclude false positives:

gosec:
  excludes:
    - G404  # math/rand is fine for non-crypto use
    - G101  # config keys flagged as credentials

depguard

Blocks imports you do not want in your codebase. Use it to enforce architectural boundaries:

depguard:
  rules:
    deprecated:
      deny:
        - pkg: io/ioutil
          desc: "deprecated since Go 1.16; use io and os"
    weak_crypto:
      deny:
        - pkg: crypto/md5
          desc: "use crypto/sha256 or crypto/sha512"

forbidigo

Bans specific function calls. Useful for enforcing structured logging over fmt.Print:

forbidigo:
  forbid:
    - pattern: '^fmt\.Print'
      msg: "Use structured logging instead of fmt.Print"
    - pattern: 'http\.DefaultClient'
      msg: "Create a client with explicit timeouts"

Run It

# Lint the whole project
golangci-lint run

# Lint with auto-fix where possible
golangci-lint run --fix

# Lint only changed files (fast CI feedback)
golangci-lint run --new-from-rev=HEAD~1

Add It to Your Taskfile

If you use Task, add a lint task:

lint:
  desc: Run golangci-lint
  cmds:
    - golangci-lint run ./...

Then task lint runs your full suite. In CI, run the same command — what you lint locally is what CI enforces.

Start Small

You do not need 70 linters on day one. Start with default: standard plus the 12 listed above. As you fix the initial findings, enable more. The config file is version‑controlled, so your whole team stays in sync.

0 views
Back to Blog

Related posts

Read more »

On Static Analysis + LLM

markdown !nwyinhttps://media2.dev.to/dynamic/image/width=50,height=50,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads...