Formatting, Vetting, and Documentation

Updated

September 13, 2026

Formatting, Vetting, and Documentation

gofmt is not a preference. go vet is not optional CI garnish. Doc comments are the API. The boring team runs all three on every change because arguments about spaces, printf verbs, and “what does this function do” are more expensive than the tools.

Mental model

gofmt takes Go source and prints Go source with one layout: tabs for indentation, a canonical import block, a standard take on line breaks. You do not configure it.

go vet runs checks the compiler skips: wrong Printf verbs, unreachable code, common lock mistakes. A vet finding is a bug even when the binary runs.

A doc comment is a complete sentence immediately above a package, type, function, or method, starting with the name. go doc prints those comments. If go doc is empty, you did not write documentation — you wrote a note somewhere else.

Worked examples

Case 1: gofmt is the style

Save as before.go. This is legal Go with noisy spacing. gofmt will rewrite it.

// before.go
package main

import "fmt"

func main() {
fmt.Println(  "tea",   2)
}

Run:

gofmt before.go

Output (stdout, file unchanged without -w):

// before.go
package main

import "fmt"

func main() {
    fmt.Println("tea", 2)
}

Write it back:

gofmt -w before.go

go fmt ./... does the same for every package under .. Editors should run gofmt on save. A pull request that bikesheds alignment is a pull request that forgot the tool.

The first-line // before.go filename comment is for this book. gofmt leaves comments alone.

Case 2: A real go vet finding, then the fix

Save as board.go. The compiler is happy. The output is not.

// board.go
package main

import "fmt"

func main() {
    table := "4"
    fmt.Printf("open table %d\n", table)
}

Run:

go vet board.go

Output:

board.go:8:25: fmt.Printf format %d has arg table of wrong type string

Run anyway:

go run board.go

Output:

open table %!d(string=4)

%d expects an integer. table is a string. Vet saw it; the runtime printed a placeholder.

Save as board.go again, with a matching verb:

// board.go
package main

import "fmt"

func main() {
    table := "4"
    fmt.Printf("open table %s\n", table)
}

Run:

go vet board.go
go run board.go

Output:

open table 4

go vet printed nothing. That is success. go vet ./... in a module is the form to keep.

Case 3: Doc comments and go doc

Save go.mod:

module example.com/desk

go 1.27

Save as shift.go:

// shift.go

// Package main is a tiny shift board for the front desk.
package main

import "fmt"

// Shift is one person's time on the desk.
type Shift struct {
    Name string
    Hour int
}

// Label returns a single-line description of the shift.
func (s Shift) Label() string {
    return fmt.Sprintf("%s at %02d:00", s.Name, s.Hour)
}

func main() {
    s := Shift{Name: "Amina", Hour: 9}
    fmt.Println(s.Label())
}

Run the program:

go run .

Output:

Amina at 09:00

Ask go doc about the type and the method (from the module directory):

go doc Shift
go doc Shift.Label

Output of go doc Shift:

type Shift struct {
    Name string
    Hour int
}
    Shift is one person's time on the desk.

func (s Shift) Label() string

Output of go doc Shift.Label:

func (s Shift) Label() string
    Label returns a single-line description of the shift.

The comment starts with the name (Shift is…, Label returns…). // Package main … documents the package. // TODO above a function is not a doc comment if it does not describe the function.

go doc fmt.Printf works the same for the standard library. You already have that documentation locally; you do not need a browser.

The trap

Turning gofmt off because “the team prefers spaces,” or ignoring vet because “it still prints.” This program documents nothing and formats nothing until you run the tools. The silent failure is the printf.

// quiet.go
package main

import "fmt"

// this helper prints a ticket id
func show(id int) {
    fmt.Printf("ticket %s\n", id)
}

func main() {
    show(7)
}

Run:

go vet quiet.go
go run quiet.go

Output:

quiet.go:8:21: fmt.Printf format %s has arg id of wrong type int
ticket %!s(int=7)

Two mistakes: %s on an int, and a comment that does not start with show so go doc show has nothing useful to say. Fix the verb (%d), rewrite the comment to // Show prints a ticket id. (and export it if it is API), run gofmt -w quiet.go.

A private formatter, a disabled vet check, or documentation in a wiki nobody updates are the same decision: the source is no longer the source of truth.

The boring rule

  • gofmt -w on every file. Tabs. No config file.
  • go vet ./... must be clean before you merge.
  • Doc comment: full sentence, starts with the name, sits on the line above the declaration.
  • // Package name … on the package clause.
  • go doc locally. Do not paste stdlib docs into your tree.
  • Do not disable a vet check to silence a real printf bug.

Try this

  1. Add a space before every ( in shift.go. Run gofmt -w shift.go and confirm the file snaps back.
  2. In the fixed board.go, change %s to %q and run the program. Then run go vet. Decide whether vet is right to stay silent (%q is valid for strings).
  3. Export show as Show in a fixed quiet.go, give it a proper doc comment, and run go doc Show.