c7e61565c0
794 lines, 35+ patterns across 9 topics with hyperlinked sources. Includes frequency data from the source (281 interfaces, 55 sentinels, 262 constructors, 309 context-accepting functions, 2685 t.Helper calls). Topics: interfaces, errors, testing, packages, concurrency, documentation, naming, configuration, extension, performance, smells. All examples are real code from the Go source, not invented.
795 lines
20 KiB
Markdown
795 lines
20 KiB
Markdown
# Go Patterns (from golang/go Source)
|
|
|
|
Prescriptive patterns extracted from the Go language source using
|
|
iterative analysis. Real examples, hyperlinked to source.
|
|
|
|
**Source:** [golang/go](https://github.com/golang/go) at commit
|
|
[`17bd5ab`](https://github.com/golang/go/tree/17bd5ab8c650155dd2bd09f7005726552639eea0)
|
|
|
|
**Stats:** 281 interfaces, 55 sentinel errors, 145 error types,
|
|
262 constructors, 309 context-accepting functions, 1,065 examples.
|
|
|
|
---
|
|
|
|
## Interface Design
|
|
|
|
### Single-Method Interfaces
|
|
|
|
**Rule:** Define interfaces with exactly one method whenever possible.
|
|
|
|
```go
|
|
type Reader interface {
|
|
Read(p []byte) (n int, err error)
|
|
}
|
|
```
|
|
|
|
**Why:** Any type with that method satisfies the interface implicitly.
|
|
Smaller interfaces = more types satisfy them = more reusable code.
|
|
|
|
**When to use:** Defining abstraction boundaries, function parameters,
|
|
dependency injection.
|
|
|
|
**When NOT to use:** When operations are genuinely inseparable
|
|
(`sort.Interface` needs Len+Less+Swap together).
|
|
|
|
**Source:** [src/io/io.go#L86](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/io/io.go#L86)
|
|
|
|
---
|
|
|
|
### Interface Composition
|
|
|
|
**Rule:** Build larger interfaces by embedding smaller ones.
|
|
|
|
```go
|
|
type ReadWriter interface {
|
|
Reader
|
|
Writer
|
|
}
|
|
|
|
type ReadWriteCloser interface {
|
|
Reader
|
|
Writer
|
|
Closer
|
|
}
|
|
```
|
|
|
|
**Why:** 15 composed interfaces in `io/io.go` from just 4 primitives
|
|
(Reader, Writer, Closer, Seeker). Composition prevents interface
|
|
bloat.
|
|
|
|
**When to use:** When callers need multiple capabilities together.
|
|
|
|
**When NOT to use:** Don't compose preemptively. Add compositions
|
|
only when you have a real function that needs both capabilities.
|
|
|
|
**Source:** [src/io/io.go#L131](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/io/io.go#L131)
|
|
|
|
---
|
|
|
|
### Accept Interfaces, Return Structs
|
|
|
|
**Rule:** Parameters should be interfaces. Return values should be
|
|
concrete types.
|
|
|
|
```go
|
|
// Accepts interface:
|
|
func Copy(dst Writer, src Reader) (written int64, err error)
|
|
|
|
// Returns concrete:
|
|
func NewReader(rd io.Reader) *Reader
|
|
```
|
|
|
|
**Why:** Accepting interfaces maximizes caller flexibility. Returning
|
|
structs gives callers full access without type assertions. 262 `New*`
|
|
constructors in stdlib all return concrete types.
|
|
|
|
**When to use:** Every public API function.
|
|
|
|
**When NOT to use:** When the return type must be hidden (use an
|
|
interface to prevent users from depending on internals).
|
|
|
|
**Source:** [src/io/io.go#L408](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/io/io.go#L408) (Copy), [src/bufio/bufio.go](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/bufio/bufio.go#L62) (NewReader)
|
|
|
|
---
|
|
|
|
### The Stringer Interface
|
|
|
|
**Rule:** Implement `String() string` for any type that has a human-
|
|
readable representation.
|
|
|
|
```go
|
|
func (t Time) String() string {
|
|
return t.Format("2006-01-02 15:04:05.999999999 -0700 MST")
|
|
}
|
|
```
|
|
|
|
**Why:** 379 types in stdlib implement Stringer. `fmt.Println` uses it
|
|
automatically. It's the Go equivalent of `__str__`.
|
|
|
|
**When to use:** Any type that might be printed or logged.
|
|
|
|
**When NOT to use:** Internal types that are never user-visible.
|
|
|
|
**Source:** [src/time/time.go](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/time/time.go) (Time.String)
|
|
|
|
---
|
|
|
|
### Type Assertion for Optional Interfaces
|
|
|
|
**Rule:** Check if a value implements an optional interface using
|
|
type assertion.
|
|
|
|
```go
|
|
if wt, ok := src.(WriterTo); ok {
|
|
return wt.WriteTo(dst)
|
|
}
|
|
```
|
|
|
|
**Why:** 104 type assertions in stdlib. This pattern allows fallback
|
|
behavior — try the fast path, fall back to the generic path.
|
|
|
|
**When to use:** Optional optimizations (WriterTo, ReaderFrom), feature
|
|
detection.
|
|
|
|
**When NOT to use:** Required behavior (just accept the interface
|
|
directly in the signature).
|
|
|
|
**Source:** [src/io/io.go#L420](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/io/io.go#L420) (Copy's WriterTo check)
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### Sentinel Errors for Known Conditions
|
|
|
|
**Rule:** Package-level `var Err*` for errors callers need to check.
|
|
Include package name in the message.
|
|
|
|
```go
|
|
var ErrBadPattern = errors.New("syntax error in pattern")
|
|
var ErrRange = errors.New("value out of range")
|
|
var ErrUnsupported = errors.New("unsupported operation")
|
|
```
|
|
|
|
**Why:** 55 exported sentinel errors in stdlib. Callers use
|
|
`errors.Is(err, strconv.ErrRange)` to handle specific cases.
|
|
|
|
**When to use:** Errors that represent documented, expected conditions
|
|
callers should distinguish.
|
|
|
|
**When NOT to use:** Errors that carry dynamic context (use error
|
|
types). Errors callers never need to identify specifically.
|
|
|
|
**Source:** [src/strconv/number.go#L246](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/strconv/number.go#L246)
|
|
|
|
---
|
|
|
|
### Error Types for Rich Context
|
|
|
|
**Rule:** Define types implementing `error` when you need structured
|
|
error information.
|
|
|
|
```go
|
|
type PathError struct {
|
|
Op string
|
|
Path string
|
|
Err error
|
|
}
|
|
|
|
func (e *PathError) Error() string {
|
|
return e.Op + " " + e.Path + ": " + e.Err.Error()
|
|
}
|
|
|
|
func (e *PathError) Unwrap() error { return e.Err }
|
|
```
|
|
|
|
**Why:** 145 error type implementations in stdlib. Callers use
|
|
`errors.As(err, &pathErr)` to extract structured data.
|
|
|
|
**When to use:** When the error needs to carry structured fields
|
|
(path, operation, underlying error).
|
|
|
|
**When NOT to use:** Simple conditions (use sentinel errors). One-off
|
|
errors (use `fmt.Errorf`).
|
|
|
|
**Source:** [src/os/error.go](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/os/error.go) (PathError)
|
|
|
|
---
|
|
|
|
### Wrap with %w
|
|
|
|
**Rule:** Add context when propagating errors. Use `%w` to preserve
|
|
the chain.
|
|
|
|
```go
|
|
return fmt.Errorf("cannot parse %q as JSON number: %w", val, strconv.ErrSyntax)
|
|
```
|
|
|
|
**Why:** 115 `%w` wrappings in stdlib. Creates a chain that
|
|
`errors.Is` and `errors.As` can traverse.
|
|
|
|
**When to use:** Every time you add context to an error from a lower
|
|
layer.
|
|
|
|
**When NOT to use:** When the original error's identity should be
|
|
hidden from callers (use `%v` to break the chain).
|
|
|
|
**Source:** [src/encoding/json/v2_decode.go#L219](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/encoding/json/v2_decode.go#L219)
|
|
|
|
---
|
|
|
|
### io.EOF as Termination Signal
|
|
|
|
**Rule:** Use `io.EOF` to signal normal end-of-stream, not an error.
|
|
|
|
```go
|
|
n, err := r.Read(buf)
|
|
if err == io.EOF {
|
|
break // Normal termination
|
|
}
|
|
if err != nil {
|
|
return err // Actual error
|
|
}
|
|
```
|
|
|
|
**Why:** 316 `io.EOF` references in stdlib. EOF is expected, not
|
|
exceptional. Readers return io.EOF when there's no more data.
|
|
|
|
**When to use:** Implementing Reader, iterators, stream processors.
|
|
|
|
**When NOT to use:** Errors that indicate failure (use a real error).
|
|
|
|
**Source:** [src/io/io.go#L44](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/io/io.go#L44)
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
### Table-Driven Tests
|
|
|
|
**Rule:** Use `[]struct{}` with named cases and `t.Run`.
|
|
|
|
```go
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
want string
|
|
}{
|
|
{"empty", "", ""},
|
|
{"hello", "hello", "HELLO"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := Transform(tt.input)
|
|
if got != tt.want {
|
|
t.Errorf("got %q, want %q", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
```
|
|
|
|
**Why:** 1,926 `t.Run` calls in the Go source. Named subtests make
|
|
failure output clear. Adding cases is one struct literal.
|
|
|
|
**When to use:** Any function with 3+ input variations.
|
|
|
|
**When NOT to use:** Tests where setup varies significantly between
|
|
cases (separate test functions).
|
|
|
|
**Source:** [src/testing/testing_test.go](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/testing/testing_test.go) (TestSetenv)
|
|
|
|
---
|
|
|
|
### t.Helper() for Test Utilities
|
|
|
|
**Rule:** Call `t.Helper()` as the first line of any test helper.
|
|
|
|
```go
|
|
func assertEqual(t *testing.T, got, want string) {
|
|
t.Helper()
|
|
if got != want {
|
|
t.Errorf("got %q, want %q", got, want)
|
|
}
|
|
}
|
|
```
|
|
|
|
**Why:** 2,685 `t.Helper()` calls. Without it, error messages report
|
|
the helper's line number instead of the caller's.
|
|
|
|
**When to use:** Every function that calls `t.Error`, `t.Fatal`, or
|
|
other testing.T methods on behalf of the caller.
|
|
|
|
**When NOT to use:** Functions that ARE the test (not helpers).
|
|
|
|
---
|
|
|
|
### Example Functions as Living Docs
|
|
|
|
**Rule:** Write `Example*` functions in `_test.go` with `// Output:`
|
|
comments.
|
|
|
|
```go
|
|
func ExampleSprintf() {
|
|
fmt.Println(fmt.Sprintf("Hello, %s", "world"))
|
|
// Output: Hello, world
|
|
}
|
|
```
|
|
|
|
**Why:** 1,065 Example functions in stdlib. They compile, run, and
|
|
appear in docs. They can't go stale.
|
|
|
|
**When to use:** Every exported function that would benefit from usage
|
|
demonstration.
|
|
|
|
**When NOT to use:** Internal APIs. Functions with non-deterministic
|
|
output.
|
|
|
|
---
|
|
|
|
### testdata/ for Fixtures
|
|
|
|
**Rule:** Put test fixtures in `testdata/` directories.
|
|
|
|
**Why:** 111 `testdata/` dirs in stdlib. The go tool ignores them
|
|
during compilation. Golden files, certificates, sample inputs live
|
|
here.
|
|
|
|
**When to use:** Files your tests read but never modify at runtime.
|
|
|
|
**When NOT to use:** Generated test data (create in TestMain).
|
|
|
|
---
|
|
|
|
### Benchmarks
|
|
|
|
**Rule:** Prefix benchmark functions with `Benchmark` and use `b.N`.
|
|
|
|
```go
|
|
func BenchmarkSprintf(b *testing.B) {
|
|
for b.Loop() {
|
|
fmt.Sprintf("hello, %s", "world")
|
|
}
|
|
}
|
|
```
|
|
|
|
**Why:** 1,974 benchmark functions in stdlib. Performance is tested,
|
|
not assumed.
|
|
|
|
**When to use:** Any code on a hot path. Any code you're optimizing.
|
|
|
|
**When NOT to use:** Code that's not performance-sensitive.
|
|
|
|
---
|
|
|
|
## Package Organization
|
|
|
|
### Flat Packages
|
|
|
|
**Rule:** No `pkg/` wrapper. Import path = directory path.
|
|
|
|
```
|
|
myproject/
|
|
├── server/
|
|
├── client/
|
|
├── internal/
|
|
└── cmd/
|
|
└── myapp/
|
|
```
|
|
|
|
**Why:** The Go stdlib has zero nesting beyond 2 levels (e.g.,
|
|
`net/http`). Import paths should be short and predictable.
|
|
|
|
**When to use:** Always.
|
|
|
|
**When NOT to use:** Never. `pkg/` is a community anti-pattern the Go
|
|
team never endorsed.
|
|
|
|
---
|
|
|
|
### internal/ for Shared Private Code
|
|
|
|
**Rule:** Code shared between packages but not part of public API
|
|
goes in `internal/`.
|
|
|
|
**Why:** 61 internal packages in stdlib. Compiler-enforced — external
|
|
code cannot import them. Stronger than unexported identifiers.
|
|
|
|
**When to use:** Utility code that multiple packages need but users
|
|
shouldn't depend on.
|
|
|
|
**When NOT to use:** Code only one package uses (keep it unexported
|
|
in that package). Code stable enough for public API (promote it).
|
|
|
|
---
|
|
|
|
### One Package, One Responsibility
|
|
|
|
**Rule:** A package does one thing. Name it with a singular noun.
|
|
|
|
`fmt`, `io`, `net`, `os`, `sync`, `time`, `bytes`, `errors`
|
|
|
|
**Why:** Package names prefix all exported identifiers. Short names
|
|
compose well: `bytes.Buffer`, `sync.Mutex`, `time.Duration`.
|
|
|
|
**When to use:** Every package.
|
|
|
|
**When NOT to use:** Never name packages `utils`, `helpers`, `common`,
|
|
`models`, or `types`.
|
|
|
|
---
|
|
|
|
## Concurrency
|
|
|
|
### context.Context as First Parameter
|
|
|
|
**Rule:** Functions that do I/O take `ctx context.Context` first.
|
|
|
|
```go
|
|
func (c *Client) Do(ctx context.Context, req *Request) (*Response, error)
|
|
```
|
|
|
|
**Why:** 309 functions take Context in stdlib. First-parameter position
|
|
is universal. Context carries cancellation, deadlines, values.
|
|
|
|
**When to use:** Any function that blocks, does I/O, or might be
|
|
cancelled.
|
|
|
|
**When NOT to use:** Pure computation. Init functions. Functions that
|
|
complete instantly.
|
|
|
|
---
|
|
|
|
### sync.Mutex for Shared State
|
|
|
|
**Rule:** Protect shared state with a mutex. Comment what it guards.
|
|
|
|
```go
|
|
type Group struct {
|
|
mu sync.Mutex // protects m
|
|
m map[string]*call // lazily initialized
|
|
}
|
|
```
|
|
|
|
**Why:** 148 Mutex/RWMutex fields in stdlib. The comment-what-it-
|
|
guards pattern appears throughout.
|
|
|
|
**When to use:** Shared mutable state accessed by multiple goroutines.
|
|
|
|
**When NOT to use:** Channel-based coordination. Single-goroutine
|
|
ownership.
|
|
|
|
**Source:** [src/internal/singleflight/singleflight.go#L30](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/internal/singleflight/singleflight.go#L30)
|
|
|
|
---
|
|
|
|
### sync.Once for Lazy Initialization
|
|
|
|
**Rule:** Use `sync.Once` for thread-safe lazy init.
|
|
|
|
```go
|
|
var defaultLogger struct {
|
|
once sync.Once
|
|
val *Logger
|
|
}
|
|
|
|
func getLogger() *Logger {
|
|
defaultLogger.once.Do(func() {
|
|
defaultLogger.val = newLogger()
|
|
})
|
|
return defaultLogger.val
|
|
}
|
|
```
|
|
|
|
**Why:** 58 `sync.Once` usages in stdlib. Guarantees exactly-once
|
|
execution regardless of concurrent callers.
|
|
|
|
**When to use:** Expensive initialization that should happen at most
|
|
once (DB connections, compiled regexps, parsed configs).
|
|
|
|
**When NOT to use:** Init that should happen at package load (use
|
|
`init()` or package-level `var`).
|
|
|
|
---
|
|
|
|
### sync.Pool for Reusable Buffers
|
|
|
|
**Rule:** Use `sync.Pool` for frequently allocated/freed objects.
|
|
|
|
```go
|
|
var encodeStatePool sync.Pool
|
|
|
|
func newEncodeState() *encodeState {
|
|
if v := encodeStatePool.Get(); v != nil {
|
|
e := v.(*encodeState)
|
|
e.Reset()
|
|
return e
|
|
}
|
|
return new(encodeState)
|
|
}
|
|
```
|
|
|
|
**Why:** Used in encoding/json, fmt, and other hot-path code. Reduces
|
|
GC pressure by reusing allocations.
|
|
|
|
**When to use:** Objects allocated per-request that are expensive to
|
|
create and safe to reuse.
|
|
|
|
**When NOT to use:** Small objects. Objects with complex cleanup.
|
|
Objects that shouldn't be shared between goroutines.
|
|
|
|
**Source:** [src/encoding/json/encode.go#L312](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/encoding/json/encode.go#L312)
|
|
|
|
---
|
|
|
|
### defer for Cleanup
|
|
|
|
**Rule:** Use `defer` immediately after acquiring a resource.
|
|
|
|
```go
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
|
|
f, err := os.Open(path)
|
|
if err != nil { return err }
|
|
defer f.Close()
|
|
```
|
|
|
|
**Why:** 329 `defer Close()`/`defer Unlock()` in stdlib. Guarantees
|
|
cleanup even on panic. Pairs acquisition with release visually.
|
|
|
|
**When to use:** Every Lock/Close/Release/Done pattern.
|
|
|
|
**When NOT to use:** Hot loops where defer overhead matters (rare,
|
|
profile first).
|
|
|
|
---
|
|
|
|
## Documentation
|
|
|
|
### Package-Level doc.go
|
|
|
|
**Rule:** Complex packages get a `doc.go` with overview documentation.
|
|
|
|
```go
|
|
// Source: src/fmt/doc.go
|
|
/*
|
|
Package fmt implements formatted I/O with functions analogous
|
|
to C's printf and scanf.
|
|
*/
|
|
package fmt
|
|
```
|
|
|
|
**Why:** 25 `doc.go` files in stdlib. Separates overview from code.
|
|
`#` headings create sections in pkg.go.dev.
|
|
|
|
**When to use:** Any package with non-trivial API surface.
|
|
|
|
**When NOT to use:** Small packages where the comment fits in the main
|
|
file.
|
|
|
|
---
|
|
|
|
### Deprecated: Comment Convention
|
|
|
|
**Rule:** Mark deprecated items with `// Deprecated: use X instead.`
|
|
|
|
**Why:** 203 `Deprecated:` comments in stdlib. Tools (editors, linters)
|
|
recognize this pattern and show warnings.
|
|
|
|
**When to use:** Any public API you want to discourage but can't
|
|
remove.
|
|
|
|
**When NOT to use:** Internal code (just delete it).
|
|
|
|
---
|
|
|
|
## Naming
|
|
|
|
### Short Package Names
|
|
|
|
**Rule:** 3-7 characters, lowercase, singular noun.
|
|
|
|
`fmt` · `io` · `net` · `os` · `sync` · `time` · `bytes` · `errors`
|
|
|
|
**When to use:** Every package.
|
|
|
|
**When NOT to use:** NEVER: `utils`, `helpers`, `common`, `base`,
|
|
`models`, `types`, `shared`.
|
|
|
|
---
|
|
|
|
### New* Constructors
|
|
|
|
**Rule:** Constructor functions are named `New` or `New<Type>`.
|
|
|
|
```go
|
|
func NewReader(rd io.Reader) *Reader
|
|
func New(text string) error
|
|
```
|
|
|
|
**Why:** 262 `New*` functions in stdlib. Universal convention. No
|
|
`Create*`, no `Make*` (except `make` builtin), no `Build*`.
|
|
|
|
**When to use:** Any function that allocates and initializes.
|
|
|
|
**When NOT to use:** Functions that transform or convert (name them
|
|
by what they do: `Parse`, `Open`, `Dial`).
|
|
|
|
---
|
|
|
|
### No Get Prefix
|
|
|
|
**Rule:** Getters don't say "Get". Setters DO say "Set".
|
|
|
|
```go
|
|
// Wrong:
|
|
func (u *User) GetName() string
|
|
|
|
// Right:
|
|
func (u *User) Name() string
|
|
func (u *User) SetName(s string)
|
|
```
|
|
|
|
**Why:** Go convention. Only 58 `Get*` methods in all of stdlib
|
|
(mostly in legacy APIs like `net/http`).
|
|
|
|
**When to use:** All accessor methods.
|
|
|
|
**When NOT to use:** RPC/protobuf generated code (follows its own
|
|
convention).
|
|
|
|
---
|
|
|
|
### MixedCaps Only
|
|
|
|
**Rule:** `ExportedName` and `unexportedName`. Never underscores.
|
|
|
|
**Why:** Capitalization IS the visibility system. Underscores are
|
|
reserved for test files and generated code.
|
|
|
|
---
|
|
|
|
## Configuration
|
|
|
|
### Functional Options (With* Pattern)
|
|
|
|
**Rule:** Options as functions returning an opaque Options type.
|
|
|
|
```go
|
|
func NewEncoder(w io.Writer, opts ...Options) *Encoder
|
|
|
|
// Option constructors:
|
|
func WithIndent(indent string) Options { ... }
|
|
func WithByteLimit(n int64) Options { ... }
|
|
```
|
|
|
|
**Why:** Growing in stdlib (encoding/json/v2, context). Allows adding
|
|
options without breaking existing callers.
|
|
|
|
**When to use:** APIs with many optional parameters that grow over
|
|
time.
|
|
|
|
**When NOT to use:** Simple APIs with 1-2 options (just use parameters
|
|
or a config struct).
|
|
|
|
**Source:** [src/encoding/json/jsontext/options.go#L232](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/encoding/json/jsontext/options.go#L232)
|
|
|
|
---
|
|
|
|
### Config Structs for Complex Setup
|
|
|
|
**Rule:** Group related options into a named struct.
|
|
|
|
```go
|
|
type Config struct {
|
|
Certificates []Certificate
|
|
RootCAs *x509.CertPool
|
|
ServerName string
|
|
MinVersion uint16
|
|
}
|
|
```
|
|
|
|
**Why:** `crypto/tls.Config` is the canonical example. Zero value is
|
|
usable with sensible defaults.
|
|
|
|
**When to use:** APIs with many related settings that configure a
|
|
long-lived object.
|
|
|
|
**When NOT to use:** Per-call options (use functional options).
|
|
|
|
**Source:** [src/crypto/tls/common.go#L566](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/crypto/tls/common.go#L566)
|
|
|
|
---
|
|
|
|
## Extension
|
|
|
|
### Register Pattern (Plugin Discovery)
|
|
|
|
**Rule:** Provide a `Register*` function for plugin architectures.
|
|
|
|
```go
|
|
func Register(name string, driver driver.Driver) {
|
|
// ...
|
|
}
|
|
```
|
|
|
|
**Why:** Used in `database/sql`, `encoding/gob`, `image`,
|
|
`archive/zip`, `crypto`. The pattern: init-time registration +
|
|
runtime lookup.
|
|
|
|
**When to use:** When users provide implementations you discover at
|
|
runtime (drivers, codecs, formats).
|
|
|
|
**When NOT to use:** When you know all implementations at compile
|
|
time (use interfaces directly).
|
|
|
|
**Source:** [src/database/sql/sql.go#L53](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/database/sql/sql.go#L53)
|
|
|
|
---
|
|
|
|
## Performance
|
|
|
|
### Append* for Zero-Alloc Formatting
|
|
|
|
**Rule:** Provide `Append*` variants that write to caller's buffer.
|
|
|
|
```go
|
|
func (t Time) AppendFormat(b []byte, layout string) []byte
|
|
func AppendEncode(dst, src []byte) []byte
|
|
func AppendQuote[Bytes ~[]byte | ~string](dst []byte, src Bytes) ([]byte, error)
|
|
```
|
|
|
|
**Why:** Growing pattern in stdlib. Avoids allocation by letting the
|
|
caller own the buffer. The `encoding` package now defines
|
|
`BinaryAppender` and `TextAppender` interfaces.
|
|
|
|
**When to use:** Hot-path formatting functions where allocation cost
|
|
matters.
|
|
|
|
**When NOT to use:** Convenience APIs where readability > performance.
|
|
|
|
**Source:** [src/time/format.go#L655](https://github.com/golang/go/blob/17bd5ab8c650155dd2bd09f7005726552639eea0/src/time/format.go#L655)
|
|
|
|
---
|
|
|
|
### Preallocate Slices
|
|
|
|
**Rule:** Use `make([]T, 0, expectedCap)` when you know the size.
|
|
|
|
**Why:** 326 `make([]T, len, cap)` calls in stdlib. Avoids repeated
|
|
reallocation during append.
|
|
|
|
**When to use:** Loops where the output size is known or estimable.
|
|
|
|
**When NOT to use:** Unknown sizes. Small slices (<8 elements).
|
|
|
|
---
|
|
|
|
## Smells
|
|
|
|
### go:linkname Abuse
|
|
|
|
1,711 uses in Go's own source — but actively being removed. If you
|
|
need `go:linkname`, your API boundary is wrong.
|
|
|
|
### TODO Without Owner
|
|
|
|
`// TODO: fix this` — unaccountable. Go's 3,428 TODOs ALL have owners.
|
|
|
|
### Get* Methods
|
|
|
|
Only 58 in stdlib, mostly legacy. Modern Go drops the prefix.
|
|
|
|
### Huge Single Files
|
|
|
|
`proc.go` is 8,156 lines. Don't copy this. The scheduler stays in one
|
|
file because splitting breaks the mental model. Your CRUD handler has
|
|
no such excuse.
|
|
|
|
### Generated Code Without Generator
|
|
|
|
If you check in generated code, also check in the generator or clearly
|
|
document regeneration steps.
|
|
|
|
<!-- PATTERN_COMPLETE -->
|