Go Standards
Go Standards
Section titled “Go Standards”1. Package Management
Section titled “1. Package Management”- Tool: Go modules (
go mod).go.modandgo.sumare both committed. - Version: Go 1.21+ (use latest stable).
- Dependencies: Use
go getto add dependencies. Keepgo.sumin sync.
2. Code Style
Section titled “2. Code Style”- Formatter:
gofmtis mandatory. Run automatically viagoimports(which also manages import groups). - Linter:
golangci-lint. Run viagolangci-lint runin CI. Treat all warnings as errors. - Vet:
go vet ./...is required in CI.
.golangci-lint.yml (minimum)
Section titled “.golangci-lint.yml (minimum)”linters: enable: - errcheck - gosimple - govet - ineffassign - staticcheck - unused - goimports - revive3. Naming Conventions
Section titled “3. Naming Conventions”- Files:
snake_case.go - Packages: Short, lowercase, no underscores (e.g.,
httputil, nothttp_util) - Exported (public) identifiers:
PascalCase - Unexported (private) identifiers:
camelCase - Constants:
PascalCase(exported) orcamelCase(unexported) — Go does not useALL_CAPS - Interfaces: Prefer single-method interfaces named by the method plus
-ersuffix (e.g.,Reader,Stringer) - Acronyms: Keep uppercase:
HTTPClient,URLParser,userID
4. Project Layout
Section titled “4. Project Layout”cmd/├── myapp/│ └── main.go # Entry points, one per binaryinternal/├── domain/ # Business logic not importable outside module│ ├── user.go│ └── user_test.go├── service/│ └── user_service.gopkg/├── httputil/ # Reusable packages safe for external import│ └── client.gocmd/— binary entry points only; minimal logic.internal/— code that must not be imported by other modules.pkg/— packages intended for external use; keep stable.- Avoid deep nesting. Flat packages are idiomatic Go.
5. Error Handling
Section titled “5. Error Handling”Go uses explicit error returns. There are no exceptions.
- Always check errors. Never use
_for an error return without a documented reason. - Wrap errors with
fmt.Errorf("context: %w", err)to preserve the error chain. - Sentinel errors: Declare with
var ErrNotFound = errors.New("not found"). Useerrors.Isto check. - Error types: Use a struct type implementing
errorwhen callers need to inspect error fields. - Panics: Reserved for programmer errors (e.g., invalid index). Never panic for expected runtime errors.
func findUser(ctx context.Context, id string) (*User, error) { user, err := db.QueryUser(ctx, id) if err != nil { return nil, fmt.Errorf("findUser %s: %w", id, err) } if user == nil { return nil, ErrNotFound } return user, nil}6. Interfaces
Section titled “6. Interfaces”Go interfaces are satisfied implicitly — no implements keyword.
- Define interfaces at the consumer, not the producer. The package that uses an interface declares it.
- Keep interfaces small. Prefer one or two methods. Compose larger interfaces from smaller ones.
- Accept interfaces, return concrete types (from functions/constructors).
// In the service package (consumer), not the storage package (producer)type UserStore interface { FindByID(ctx context.Context, id string) (*User, error) Save(ctx context.Context, user *User) error}7. Concurrency
Section titled “7. Concurrency”- Goroutines: Always document the lifecycle of every goroutine. Know when it exits.
- Channels: Prefer channels for communication; use
syncprimitives for state protection. - Context: Pass
context.Contextas the first argument to functions that can block or be cancelled. - WaitGroups: Use
sync.WaitGroupto wait for a group of goroutines to complete. - Race detector: Run
go test -race ./...in CI. All races must be fixed. - Avoid goroutine leaks. Use
contextcancellation ordonechannels to signal goroutines to stop.
func processAll(ctx context.Context, items []Item) error { g, ctx := errgroup.WithContext(ctx) for _, item := range items { item := item // capture loop variable g.Go(func() error { return process(ctx, item) }) } return g.Wait()}8. Testing
Section titled “8. Testing”- Framework: Built-in
testingpackage. Run withgo test ./.... - Table-driven tests: Preferred for functions with multiple input/output cases.
- Test files:
_test.gosuffix in the same package (white-box) or_testpackage suffix (black-box). - Mocking: Use interface-based test doubles.
github.com/stretchr/testify/mockor hand-written fakes. - Assertions:
github.com/stretchr/testify/assertandrequirefor cleaner assertions. - Coverage:
go test -cover ./.... 95% minimum for any package. Target 100% for domain logic.
func TestFindUser(t *testing.T) { tests := []struct { name string id string want *User wantErr error }{ {"found", "123", &User{ID: "123"}, nil}, {"not found", "999", nil, ErrNotFound}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { got, err := findUser(context.Background(), tc.id) require.ErrorIs(t, err, tc.wantErr) assert.Equal(t, tc.want, got) }) }}9. Documentation
Section titled “9. Documentation”- Exported identifiers must have a godoc comment starting with the identifier name.
- Package comment: Every package has a
// Package <name> ...comment in one file. - Format: Plain text.
godocrenders it — no Markdown.
// Package user provides domain types and business rules for user management.package user
// User represents an authenticated member of the system.type User struct { ID string Email string}
// FindByEmail returns the user with the given email address,// or ErrNotFound if no such user exists.func FindByEmail(ctx context.Context, email string) (*User, error) { // ...}10. Dependencies
Section titled “10. Dependencies”Common Packages
Section titled “Common Packages”- HTTP routing:
net/httpstdlib +github.com/go-chi/chiorgithub.com/gin-gonic/gin - Database:
database/sqlwithgithub.com/jackc/pgx(Postgres) orgorm.io/gorm - Migrations:
github.com/golang-migrate/migrate - Config:
github.com/spf13/viperorgithub.com/caarlos0/env - Logging:
log/slog(stdlib, Go 1.21+) orgo.uber.org/zap - Testing:
github.com/stretchr/testify - Concurrency helpers:
golang.org/x/sync/errgroup
11. Performance
Section titled “11. Performance”- Preallocate slices when length is known:
make([]T, 0, n). - Profile before optimizing. Use
go tool pprofwith CPU and heap profiles. - Avoid premature optimization. Write clear code first; profile in production-like conditions.
- String building: Use
strings.Builderinstead of repeated concatenation.
12. Security
Section titled “12. Security”- Input validation: Validate and sanitize all external input. Use
html/template(nottext/template) for HTML output to prevent XSS. - SQL injection: Use parameterized queries exclusively. Never interpolate user input into SQL strings.
- SAST: Run
golangci-lintwithgosecenabled in CI (github.com/securego/gosec). - Dependency scanning: Run
govulncheck ./...in CI (golang.org/x/vuln/cmd/govulncheck). unsafepackage: Do not useunsafeunless absolutely necessary. Document every usage with the invariant that makes it safe.- Cryptography: Use
crypto/randfor random values. Never usemath/randfor security purposes. - See sec-01_security_standards.md for the complete banned-functions list with language-specific examples.