Go Quality - Quick Reference
When NOT to Use This Skill
-
SonarQube setup - Use sonarqube skill
-
Security scanning - Use go-security skill
-
Testing - Use Go testing skills
Deep Knowledge: Use mcp__documentation__fetch_docs with technology: go for comprehensive documentation.
Tool Overview
Tool Focus Included In
go fmt Formatting Go toolchain
go vet Bug detection Go toolchain
staticcheck Advanced analysis golangci-lint
golangci-lint Meta-linter Standalone
gofumpt Stricter formatting golangci-lint
Recommendation: Use golangci-lint - it runs 100+ linters in parallel.
golangci-lint Setup
Installation
Binary installation (recommended)
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.56.0
Go install
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
macOS
brew install golangci-lint
.golangci.yml
run: timeout: 5m tests: true go: "1.22"
linters: enable: # Bugs - bodyclose - durationcheck - errcheck - exportloopref - gosec - nilerr - sqlclosecheck
# Performance
- prealloc
# Style
- gofumpt
- goimports
- misspell
# Complexity
- cyclop
- gocognit
- gocyclo
- funlen
- nestif
# Best Practices
- goconst
- gocritic
- godot
- ineffassign
- revive
- staticcheck
- unconvert
- unparam
- unused
linters-settings: cyclop: max-complexity: 10
gocognit: min-complexity: 15
gocyclo: min-complexity: 10
funlen: lines: 60 statements: 40
nestif: min-complexity: 4
goconst: min-len: 3 min-occurrences: 3
gocritic: enabled-tags: - diagnostic - style - performance disabled-checks: - hugeParam
revive: rules: - name: blank-imports - name: context-as-argument - name: context-keys-type - name: dot-imports - name: error-naming - name: error-return - name: error-strings - name: exported - name: increment-decrement - name: indent-error-flow - name: package-comments - name: range - name: receiver-naming - name: time-naming - name: unexported-return - name: var-declaration - name: var-naming
issues: exclude-rules: # Exclude some linters from tests - path: _test.go linters: - funlen - gocognit
# Exclude generated files
- path: \.pb\.go
linters:
- all
max-same-issues: 0 max-issues-per-linter: 0
Commands
Run all linters
golangci-lint run
Run on specific path
golangci-lint run ./pkg/...
Fix auto-fixable issues
golangci-lint run --fix
Show enabled linters
golangci-lint linters
Run specific linter
golangci-lint run --enable=gosec --disable-all
Verbose output
golangci-lint run -v
Built-in Go Tools
go fmt / gofmt
Format file
go fmt ./...
Check without changing
gofmt -l .
Simplify code
gofmt -s -w .
go vet
Run vet
go vet ./...
Specific checks
go vet -printf ./...
gofumpt (Stricter Formatting)
Install
go install mvdan.cc/gofumpt@latest
Format
gofumpt -w .
Common Linter Errors
errcheck - Unhandled Errors
// BAD - Error ignored file, _ := os.Open("file.txt")
// GOOD - Error handled file, err := os.Open("file.txt") if err != nil { return fmt.Errorf("failed to open file: %w", err) } defer file.Close()
bodyclose - Unclosed Response Body
// BAD - Body not closed resp, err := http.Get(url) if err != nil { return err } data, _ := io.ReadAll(resp.Body)
// GOOD - Body closed resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() data, err := io.ReadAll(resp.Body)
gosec - Security Issues
// BAD - Hardcoded credentials (G101) const password = "secret123"
// GOOD - Environment variable password := os.Getenv("DB_PASSWORD") if password == "" { return errors.New("DB_PASSWORD not set") }
ineffassign - Ineffective Assignment
// BAD - Assignment overwritten x := 5 x = 10 // Previous value never used
// GOOD - Remove or use x := 10
cyclop/gocyclo - High Complexity
// BAD - Complex function func process(data Data) error { if data.Type == "A" { if data.Status == "active" { if data.Value > 0 { // deep nesting } } } // ... many more conditions }
// GOOD - Extract and simplify func process(data Data) error { if !data.isValid() { return ErrInvalidData }
switch data.Type {
case "A":
return processTypeA(data)
case "B":
return processTypeB(data)
default:
return ErrUnknownType
}
}
Common Code Smells & Fixes
- Error Handling
// BAD - Ignoring errors data, _ := json.Marshal(obj)
// BAD - Generic error message if err != nil { return errors.New("error occurred") }
// GOOD - Wrap with context data, err := json.Marshal(obj) if err != nil { return fmt.Errorf("failed to marshal user %d: %w", user.ID, err) }
- Context Propagation
// BAD - No context func fetchUser(id int) (*User, error) { return db.Query("SELECT * FROM users WHERE id = ?", id) }
// GOOD - Context first parameter func fetchUser(ctx context.Context, id int) (*User, error) { return db.QueryContext(ctx, "SELECT * FROM users WHERE id = ?", id) }
- Interface Pollution
// BAD - Interface with many methods type UserService interface { GetUser(id int) (*User, error) CreateUser(u *User) error UpdateUser(u *User) error DeleteUser(id int) error ListUsers() ([]*User, error) SearchUsers(q string) ([]*User, error) // ... 10 more methods }
// GOOD - Small, focused interfaces type UserGetter interface { GetUser(ctx context.Context, id int) (*User, error) }
type UserCreator interface { CreateUser(ctx context.Context, u *User) error }
// Compose when needed type UserService interface { UserGetter UserCreator }
- Package Structure
// BAD - God package package app
func CreateUser() {} func CreateOrder() {} func SendEmail() {} func GeneratePDF() {}
// GOOD - Focused packages // user/service.go package user func Create(ctx context.Context, u *User) error {}
// order/service.go package order func Create(ctx context.Context, o *Order) error {}
- Receiver Naming
// BAD - Long receiver names func (userService *UserService) GetUser(id int) {} func (this *UserService) CreateUser(u *User) {}
// GOOD - Short, consistent names func (s *UserService) GetUser(id int) {} func (s *UserService) CreateUser(u *User) {}
// Or even shorter for simple types func (u *User) FullName() string {}
Pre-commit Setup
.pre-commit-config.yaml
repos:
-
repo: https://github.com/golangci/golangci-lint rev: v1.56.0 hooks:
- id: golangci-lint
-
repo: local hooks:
-
id: go-fmt name: go fmt entry: gofmt -w language: system types: [go]
-
id: go-vet name: go vet entry: go vet ./... language: system types: [go] pass_filenames: false
-
Makefile
.PHONY: lint fmt vet test quality
lint: golangci-lint run
fmt: gofumpt -w .
vet: go vet ./...
test: go test -race -cover ./...
quality: fmt vet lint test
VS Code Settings
// .vscode/settings.json { "go.lintTool": "golangci-lint", "go.lintFlags": ["--fast"], "go.formatTool": "gofumpt", "[go]": { "editor.formatOnSave": true, "editor.codeActionsOnSave": { "source.organizeImports": "explicit" } }, "gopls": { "staticcheck": true, "analyses": { "unusedparams": true, "shadow": true } } }
Quality Metrics Targets
Metric Target Linter
Cyclomatic Complexity < 10 cyclop, gocyclo
Cognitive Complexity < 15 gocognit
Function Lines < 60 funlen
Nesting Depth < 4 nestif
Test Coverage
80% go test
CI/CD Integration
GitHub Actions
name: Quality on: [push, pull_request]
jobs: quality: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.22"
- name: golangci-lint
uses: golangci/golangci-lint-action@v4
with:
version: v1.56.0
- name: Run tests
run: go test -race -coverprofile=coverage.txt ./...
- name: Upload coverage
uses: codecov/codecov-action@v4
with:
files: coverage.txt
Anti-Patterns
Anti-Pattern Why It's Bad Correct Approach
_ = err (ignoring) Hides failures Handle or log errors
No context in functions Can't cancel/timeout Pass context as first param
Giant interfaces Hard to mock/test Small, focused interfaces
Package-level vars Global state Dependency injection
//nolint without reason Hides issues Add comment explaining why
init() abuse Hidden initialization Explicit setup functions
Quick Troubleshooting
Issue Likely Cause Solution
golangci-lint timeout Too many linters Increase timeout or use --fast
Conflicting linter rules Multiple formatters Disable duplicates
False positive Linter bug or edge case Add //nolint:linter with comment
Missing go.sum entries Incomplete module Run go mod tidy
Import grouping wrong goimports config Configure in .golangci.yml
Related Skills
-
SonarQube
-
Clean Code
-
Go Security