Formatting, Vetting, and Documentation
Overview
Go has a unique culture: there’s one blessed way to format code, a built-in static analyzer for common mistakes, and a documentation system that extracts docs from comments. This chapter covers gofmt, go vet, and godoc standards.
Code Formatting with gofmt
Why Gofmt Exists
Unlike other languages where style guides vary (tabs vs. spaces, brace placement), Go has a single canonical format enforced by gofmt. This eliminates style debates and ensures all Go code looks the same.
“Gofmt’s style is no one’s favorite, yet gofmt is everyone’s favorite.” — Rob Pike
Basic Usage
# Format a file (print to stdout)
$ gofmt main.go
# Format and overwrite file
$ gofmt -w main.go
# Format all files recursively
$ gofmt -w .
# Using go fmt (recommended)
$ go fmt ./...Formatting Rules
gofmt enforces:
// BEFORE (your style)
func foo(x int,y string){
if x>0{
return
}
}
// AFTER (gofmt style)
func foo(x int, y string) {
if x > 0 {
return
}
}| Rule | Gofmt Standard |
|---|---|
| Indentation | Tabs |
| Spacing | Spaces around operators |
| Braces | Opening brace on same line |
| Line length | No limit (but keep reasonable) |
| Blank lines | Strategic for readability |
Code Simplification
Use -s to simplify code:
$ gofmt -s -w main.go// Before
s[a:len(s)]
for x, _ := range v {}
[]int{1, 2, 3}[0:2]
// After -s
s[a:]
for x := range v {}
[]int{1, 2, 3}[:2]Import Formatting with goimports
goimports does everything gofmt does, plus it manages imports:
# Install
$ go install golang.org/x/tools/cmd/goimports@latest
# Format and fix imports
$ goimports -w main.go// Before (missing import, unused import)
package main
import (
"unused"
)
func main() {
fmt.Println("Hello")
}
// After goimports
package main
import "fmt"
func main() {
fmt.Println("Hello")
}Static Analysis with go vet
What Vet Catches
go vet detects code that compiles but is probably wrong:
$ go vet ./...Common Issues Detected
Printf Format Errors
// go vet catches this
fmt.Printf("%d", "string") // wrong type
fmt.Printf("%s %s", name) // wrong number of argsUnreachable Code
func example() int {
return 42
fmt.Println("never runs") // go vet: unreachable code
}Suspicious Loop Variables
// go vet warns about this common bug
for i, v := range values {
go func() {
fmt.Println(i, v) // captures loop variable
}()
}Useless Assignments
x := 5
x = x // go vet: self-assignmentRunning Specific Checks
# List available analyzers
$ go tool vet help
# Run specific analyzer
$ go vet -composites=false ./...
$ go vet -printf=true ./...Enhanced Linting with staticcheck
staticcheck provides additional checks beyond go vet:
# Install
$ go install honnef.co/go/tools/cmd/staticcheck@latest
# Run
$ staticcheck ./...Checks include: - Unused code - Deprecated function usage - Simplification suggestions - Performance improvements - Common bugs
Documentation with godoc
Writing Documentation
Go extracts documentation from comments directly before declarations:
// Package math provides basic mathematical operations.
// It includes functions for arithmetic, trigonometry, and more.
package math
// Pi represents the mathematical constant π.
const Pi = 3.14159
// Add returns the sum of two integers.
// It handles overflow by wrapping around.
func Add(a, b int) int {
return a + b
}
// Calculator provides stateful mathematical operations.
type Calculator struct {
// Result holds the current calculation result.
Result float64
}
// Add adds n to the current result.
func (c *Calculator) Add(n float64) {
c.Result += n
}Documentation Conventions
| Rule | Example |
|---|---|
| Start with name | // Add returns the sum... |
| Complete sentences | End with period |
| First sentence is summary | Shown in package lists |
| Blank line for paragraphs | Separate blocks |
Code Examples in Docs
Create examples in *_test.go files:
// example_test.go
package math_test
import (
"fmt"
"myproject/math"
)
func ExampleAdd() {
result := math.Add(2, 3)
fmt.Println(result)
// Output: 5
}
func ExampleCalculator_Add() {
c := &math.Calculator{}
c.Add(10)
c.Add(5)
fmt.Println(c.Result)
// Output: 15
}These examples: - Appear in documentation - Are tested by go test - Show real usage
Viewing Documentation
# Command line
$ go doc fmt
$ go doc fmt.Println
$ go doc -all fmt
# Local web server
$ go install golang.org/x/tools/cmd/godoc@latest
$ godoc -http=:6060
# Visit http://localhost:6060Package Comments
For larger packages, use a doc.go file:
// doc.go
/*
Package server implements an HTTP server with middleware support.
# Getting Started
Create a new server and add routes:
srv := server.New()
srv.Get("/", handleHome)
srv.Listen(":8080")
# Middleware
Add middleware to process all requests:
srv.Use(server.Logger())
srv.Use(server.Recovery())
# Configuration
Configure the server using options:
srv := server.New(
server.WithTimeout(30 * time.Second),
server.WithMaxBodySize(1 << 20),
)
*/
package serverCombining Tools in Workflow
Pre-commit Hook
#!/bin/bash
# .git/hooks/pre-commit
# Format
gofmt -l -w .
# Vet
go vet ./...
if [ $? -ne 0 ]; then
echo "go vet failed"
exit 1
fi
# Staticcheck
staticcheck ./...
if [ $? -ne 0 ]; then
echo "staticcheck failed"
exit 1
fiMakefile Integration
.PHONY: check fmt vet lint
fmt:
gofmt -w .
vet:
go vet ./...
lint:
staticcheck ./...
check: fmt vet lint
@echo "All checks passed"CI Pipeline (GitHub Actions)
- name: Check formatting
run: |
if [ "$(gofmt -l . | wc -l)" -gt 0 ]; then
echo "Code is not formatted"
gofmt -d .
exit 1
fi
- name: Vet
run: go vet ./...
- name: Staticcheck
run: |
go install honnef.co/go/tools/cmd/staticcheck@latest
staticcheck ./...Summary
| Tool | Purpose | Usage |
|---|---|---|
gofmt |
Format code | gofmt -w . |
goimports |
Format + manage imports | goimports -w . |
go vet |
Catch common mistakes | go vet ./... |
staticcheck |
Enhanced linting | staticcheck ./... |
godoc |
Generate documentation | godoc -http=:6060 |
More examples
Example: godoc-style exported comments
Save as main.go and go run . (with go mod init example if needed).
package main
import (
"fmt"
"strings"
)
// Title returns s with the first letter of each space-separated word uppercased.
// Empty input yields an empty string.
func Title(s string) string {
parts := strings.Fields(strings.ToLower(s))
for i, p := range parts {
if p == "" {
continue
}
parts[i] = strings.ToUpper(p[:1]) + p[1:]
}
return strings.Join(parts, " ")
}
// JoinNonEmpty joins non-empty parts with sep.
func JoinNonEmpty(sep string, parts ...string) string {
var keep []string
for _, p := range parts {
if p != "" {
keep = append(keep, p)
}
}
return strings.Join(keep, sep)
}
func main() {
fmt.Println(Title("hello go"))
fmt.Println(JoinNonEmpty("/", "a", "", "b"))
}Expected:
Hello Go
a/b
Example: a construct go vet cares about
Save as main.go and go run . (with go mod init example if needed).
package main
import "fmt"
func main() {
// Lock-free counter demo is fine; the classic vet catch is printf args.
name := "gopher"
// Correct: verbs match arguments.
fmt.Printf("hello %s count=%d\n", name, 3)
// Intentionally correct form of a common mistake:
// fmt.Printf("hello %s\n", name, 3) // vet: Printf format %s reads arg but has 2 extra
fmt.Println("vet clean: format verbs match args")
}Expected:
hello gopher count=3
vet clean: format verbs match args
Runnable example
Documented helpers in gofmt style—run the program, then try go doc / go vet on the same files.
Save as main.go. From an empty directory:
go mod init example
go fmt .
go vet .
go run .
go doc -all .package main
import (
"fmt"
"strings"
)
// TitleCase returns s with the first letter of each word uppercased.
// Words are split on Unicode whitespace.
func TitleCase(s string) string {
fields := strings.Fields(s)
for i, w := range fields {
if w == "" {
continue
}
// Manual title for a tiny demo; prefer cases.Title in real code.
r := []rune(w)
r[0] = []rune(strings.ToUpper(string(r[0])))[0]
if len(r) > 1 {
fields[i] = string(r[0]) + strings.ToLower(string(r[1:]))
} else {
fields[i] = string(r[0])
}
}
return strings.Join(fields, " ")
}
// Sum returns the sum of nums. An empty slice sums to 0.
func Sum(nums []int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func main() {
fmt.Println(TitleCase("hello gofmt and godoc"))
fmt.Printf("sum=%d\n", Sum([]int{1, 2, 3, 4}))
// Intentional-looking pattern that go vet is happy with:
fmt.Printf("count=%d name=%s\n", 2, "tools")
}Expected output (illustrative):
Hello Gofmt And Godoc
sum=10
count=2 name=tools
What to notice: - Comments sit directly above TitleCase and Sum—go doc surfaces them. - go fmt will rewrite spacing/imports if you mess them up on purpose. - go vet checks printf verbs match argument types (try changing %d to %s). - Zero-value-friendly API: empty slice → sum 0, no panic.
Try next: Break the Printf format string (fmt.Printf("%d", "x")) and run go vet to see the diagnostic.