Introduction
Go’s philosophy of simplicity extends to its tooling — go fmt ships with the compiler and settles formatting debates permanently. But format consistency is just the first layer of code quality. Modern Go projects need static analysis to catch bugs, security scanning to prevent vulnerabilities, and style enforcement to maintain readability across teams.
The Go ecosystem has responded with a rich set of code quality tools that go far beyond the standard library. golangci-lint bundles dozens of linters into a unified runner. staticcheck provides deep static analysis that catches correctness issues missed by the compiler. revive offers a highly configurable, extensible linting framework. gofumpt enforces a stricter, more opinionated formatting standard. And gosec scans for common security vulnerabilities in Go source code.
In this guide, we compare these five tools, show how to integrate them into development workflows, and provide configuration examples for teams of all sizes.
Tool Comparison
| Feature | golangci-lint | staticcheck | revive | gofumpt | gosec |
|---|---|---|---|---|---|
| Type | Linter aggregator | Static analyzer | Linter framework | Formatter | Security scanner |
| Stars | ~16,000 | ~7,000 | ~5,000 | ~3,000 | ~8,000 |
| Linters/Checks | 50+ bundled | 150+ checks | 80+ rules | N/A (formatter) | 30+ rules |
| Configuration | YAML (.golangci.yml) | Config file (optional) | TOML (revive.toml) | CLI flags | CLI flags + config |
| Performance | Fast (parallel) | Fast (~2s/project) | Fast | Very fast | Moderate |
| Auto-fix | Via –fix flag | Some auto-fixes | No | Yes (always) | No |
| CI Integration | GitHub Actions, GitLab CI | GitHub Actions | GitHub Actions | Pre-commit hook | GitHub Actions |
| Custom Rules | Via plugins | No | Yes (custom rules) | No | Via custom rules |
| IDE Support | VS Code, GoLand | VS Code, GoLand | VS Code, GoLand | Built into Go toolchain | VS Code, GoLand |
golangci-lint: The Unified Linter Runner
golangci-lint integrates over 50 linters — including staticcheck, gosec, revive, errcheck, and govet — behind a single command. It runs linters in parallel, caches results, and provides a unified output format.
| |
| |
golangci-lint’s key advantage is running all linters in a single pass. Without it, you’d run staticcheck, gosec, revive, errcheck, and go vet separately — each parsing the AST independently. By sharing the AST across linters, golangci-lint reduces total analysis time by 60-70% compared to sequential execution.
staticcheck: Deep Static Analysis
staticcheck (package honnef.co/go/tools) performs sophisticated static analysis that catches bugs the Go compiler cannot detect. It’s maintained by Dominik Honnef and is the most thorough static analyzer in the Go ecosystem.
| |
| |
staticcheck’s checks are organized into categories: SA (static analysis, correctness), S (simplifications), ST (style), and QF (quick fixes). The SA checks are the most valuable — they detect nil pointer dereferences, infinite recursion, time.Tick leaks, mutex copy issues, and incorrectly formatted struct tags. At ~7,000 stars, staticcheck is widely trusted; it’s included in golangci-lint’s default enabled linters.
revive: Extensible Linting
revive is a drop-in replacement for golint that adds speed, extensibility, and configurability. Each rule is a standalone Go package, making it trivial to write custom rules for your organization’s conventions.
| |
| |
revive’s extensibility is its standout feature. Writing a custom rule takes under 50 lines:
| |
gofumpt: Stricter Formatting
gofumpt (mvdan.cc/gofumpt) is a stricter go fmt that enforces additional formatting rules. It’s fully backward-compatible — any gofumpt-formatted code passes go fmt.
| |
Key differences from go fmt:
- No empty lines at the beginning and end of functions
- Composite literals use consistent field alignment
- Octal literals must use
0oprefix (not legacy0prefix) - Comments in empty
caseclauses are formatted consistently - Multi-line function signatures collapse extra lines
| |
gofumpt integrates into golangci-lint by adding gofumpt to the enabled linters list. It’s also available as a VS Code format-on-save provider via the Go extension settings.
gosec: Security Scanning
gosec (github.com/securego/gosec) inspects Go source code for security vulnerabilities by examining the AST for unsafe patterns. It detects SQL injection, hardcoded credentials, weak crypto, file inclusion, and TLS misconfigurations.
| |
| |
gosec’s rules are organized by severity (low/medium/high) and confidence. In CI pipelines, it’s common to fail builds on medium severity and above. The #nosec annotation suppresses false positives on specific lines.
CI/CD Integration Pipeline
All five tools integrate into a unified CI workflow. Here’s a production-grade GitHub Actions configuration:
| |
Performance and Integration Patterns
golangci-lint caches results aggressively — on the second run with an unchanged codebase, analysis completes in under 100ms. The --new-from-rev flag checks only changed code relative to a git reference, making it practical to run in pre-commit hooks:
| |
For monorepos with multiple Go modules, golangci-lint’s --path-prefix flag handles per-module configurations. Each module can have its own .golangci.yml, with a root config defining shared baseline settings.
Why Go Code Quality Tooling Is Exceptional
Go’s tooling ecosystem is uniquely cohesive among compiled languages. While Rust relies on clippy and rustfmt as separate workflows, and C++ requires external tools with complex build integration, Go’s code quality tools are all installable via go install, operate on the standard AST, and compose through golangci-lint’s unified interface. This integration quality is one reason Go teams report fewer style debates and security incidents than teams in other ecosystems. For a broader look at code quality across languages, see our comparison of code linter and formatter tools and our guide to Go testing frameworks.
FAQ
What’s the difference between go fmt, gofumpt, and goimports?
go fmt is the standard formatter shipped with Go — it handles indentation, spacing, and line breaks. gofumpt adds stricter rules on top (no empty lines, consistent composite literal alignment, octal literal enforcement). goimports handles import organization and unused import removal. They’re complementary: use gofumpt for formatting and goimports for import management. golangci-lint can run all three.
Should I use golangci-lint or run linters individually?
Use golangci-lint. It’s faster (shared AST parsing), produces unified output, and is the de facto standard for Go projects. Individual linters make sense when you need a specific feature golangci-lint doesn’t support, but for 95% of projects, golangci-lint is all you need.
How do I handle false positives from gosec?
gosec’s security checks are intentionally cautious — false positives happen. Suppress them on specific lines with a // #nosec comment, or exclude entire rules in the golangci-lint config under issues.exclude-rules. Avoid disabling gosec globally — the noise is a signal that your code has patterns worth reviewing.
Can I use revive instead of golangci-lint?
revive is a single linter (style and correctness rules), while golangci-lint runs revive plus 50+ other linters. You can run revive standalone for its custom rules feature, but for most teams, running revive as part of golangci-lint gives you revive’s checks plus staticcheck, gosec, errcheck, and others in one command. If you need custom revive rules, configure them in .golangci.yml under linters-settings.revive.rules.
How do I enforce code quality in a legacy Go codebase?
Start with golangci-lint run --new-from-rev=HEAD~1 to only check changed code. Configure a minimal linter set (govet, errcheck, staticcheck) initially. Use --issues-exit-code=0 to report issues without failing CI. Over several sprints, gradually enable additional linters (gocognit, gocyclo, gosec) and increase severity. Auto-fix what you can with --fix. For a related approach to testing legacy codebases, see our property-based testing guide.
💰 想测试你的市场判断力?我用 Polymarket 做预测市场交易——这是全球最大的预测市场平台,从大选结果到技术监管时间线,什么都可以押注。和赌博不同,这是真正的信息市场:你懂的信息越多,胜率越高。我靠预测技术相关事件的走向已经赚了不少。用我的邀请链接注册:Polymarket.com