feat: initial Go patterns guide from stdlib + Kubernetes source study
9 pattern files covering stdlib (structs, interfaces, API conventions, docs, style), Kubernetes (controller/reconciler, informer/cache, leader election, code generation), comparison (stdlib vs K8s approaches), and anti-patterns. All patterns cite exact source files and line numbers.
This commit is contained in:
@@ -0,0 +1,374 @@
|
||||
# API Conventions in the Go Standard Library
|
||||
|
||||
## 1. The Must Pattern
|
||||
|
||||
**Pattern name:** MustXxx (Panic on Error)
|
||||
|
||||
**Source citation:** `regexp/regexp.go` lines 310–320, `text/template/helper.go` lines 19–30
|
||||
|
||||
**What it does:** A function wraps a fallible constructor and panics if the error
|
||||
is non-nil. Named `MustXxx` or `Must` (when wrapping a generic `(T, error)` pair).
|
||||
|
||||
**Why:** Safe initialization of package-level variables at program startup. Since
|
||||
`var` initializers can't handle errors, `Must` converts programmer errors (bad
|
||||
regex literals, bad templates) into immediate panics that surface during init.
|
||||
|
||||
**Anti-pattern:** Using Must in runtime code where the input is dynamic/user-provided;
|
||||
panicking on recoverable errors; naming it something other than Must (e.g., `PanicOnError`).
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// regexp/regexp.go:310-320
|
||||
// MustCompile is like [Compile] but panics if the expression cannot be parsed.
|
||||
// It simplifies safe initialization of global variables holding compiled regular
|
||||
// expressions.
|
||||
func MustCompile(str string) *Regexp {
|
||||
regexp, err := Compile(str)
|
||||
if err != nil {
|
||||
panic(`regexp: Compile(` + quote(str) + `): ` + err.Error())
|
||||
}
|
||||
return regexp
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// text/template/helper.go:19-30
|
||||
// Must is a helper that wraps a call to a function returning ([*Template], error)
|
||||
// and panics if the error is non-nil. It is intended for use in variable
|
||||
// initializations such as
|
||||
//
|
||||
// var t = template.Must(template.New("name").Parse("text"))
|
||||
func Must(t *Template, err error) *Template {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Compile / MustCompile Pair
|
||||
|
||||
**Pattern name:** Fallible Constructor + Must Wrapper
|
||||
|
||||
**Source citation:** `regexp/regexp.go` lines 130–131, 310–320
|
||||
|
||||
**What it does:** The real constructor returns `(*T, error)`. A parallel `Must` variant
|
||||
wraps it for use in global variable initialization.
|
||||
|
||||
**Why:** Separates concerns: `Compile` is for runtime use where errors are handled;
|
||||
`MustCompile` is for compile-time-known values where failure is a programming bug.
|
||||
|
||||
**Anti-pattern:** Only providing the Must variant (no way to handle errors gracefully);
|
||||
only providing the error variant (verbose for package-level vars).
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// regexp/regexp.go:130-131
|
||||
func Compile(expr string) (*Regexp, error) {
|
||||
return compile(expr, syntax.Perl, false)
|
||||
}
|
||||
|
||||
// regexp/regexp.go:310-315
|
||||
func MustCompile(str string) *Regexp {
|
||||
regexp, err := Compile(str)
|
||||
if err != nil {
|
||||
panic(`regexp: Compile(` + quote(str) + `): ` + err.Error())
|
||||
}
|
||||
return regexp
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. XxxWithContext Variant
|
||||
|
||||
**Pattern name:** WithContext Function Overload
|
||||
|
||||
**Source citation:** `net/http/request.go` lines 867–869, 894–930
|
||||
|
||||
**What it does:** Provides two function variants: `NewRequest` (uses `context.Background()`)
|
||||
and `NewRequestWithContext` (accepts an explicit context). The simple version delegates
|
||||
to the context-aware one.
|
||||
|
||||
**Why:** Context was added after the original API was established. The `WithContext`
|
||||
variant enables cancellation and deadlines; the plain variant preserves backward
|
||||
compatibility and ergonomics for the common case.
|
||||
|
||||
**Anti-pattern:** Breaking the existing API signature; always requiring context even
|
||||
for fire-and-forget uses; naming it `NewRequestCtx`.
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// net/http/request.go:867-869
|
||||
func NewRequest(method, url string, body io.Reader) (*Request, error) {
|
||||
return NewRequestWithContext(context.Background(), method, url, body)
|
||||
}
|
||||
|
||||
// net/http/request.go:894+
|
||||
func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
|
||||
// full implementation...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Nil-Opts Convention (Optional Config Pointer)
|
||||
|
||||
**Pattern name:** `*Options` Parameter — Nil Means Defaults
|
||||
|
||||
**Source citation:** `log/slog/text_handler.go` lines 28–42, `log/slog/handler.go` lines 135–175
|
||||
|
||||
**What it does:** A constructor accepts a pointer to an options struct. If the pointer
|
||||
is nil, all defaults apply. The constructor internally substitutes a zero-value struct.
|
||||
|
||||
**Why:** Keeps the simple case clean (`NewTextHandler(os.Stderr, nil)`) while allowing
|
||||
full customization. The pointer type signals "this entire argument is optional."
|
||||
|
||||
**Anti-pattern:** Requiring a non-nil options struct even with zero customization;
|
||||
using variadic functional options when a simple struct suffices.
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// log/slog/text_handler.go:28-42
|
||||
// NewTextHandler creates a [TextHandler] that writes to w,
|
||||
// using the given options.
|
||||
// If opts is nil, the default options are used.
|
||||
func NewTextHandler(w io.Writer, opts *HandlerOptions) *TextHandler {
|
||||
if opts == nil {
|
||||
opts = &HandlerOptions{}
|
||||
}
|
||||
return &TextHandler{
|
||||
&commonHandler{
|
||||
json: false,
|
||||
w: w,
|
||||
opts: *opts,
|
||||
mu: &sync.Mutex{},
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Builder Pattern (Accumulate + Finalize)
|
||||
|
||||
**Pattern name:** Builder (Write Methods + String/Bytes Finalizer)
|
||||
|
||||
**Source citation:** `strings/builder.go` lines 14–113
|
||||
|
||||
**What it does:** A zero-value struct accumulates data via Write/WriteByte/WriteString
|
||||
methods, then produces a final result via String(). The builder is not reusable after
|
||||
a copyCheck-protected modification.
|
||||
|
||||
**Why:** Avoids repeated string concatenation (O(n²) allocations). The zero value is
|
||||
ready to use. Implements `io.Writer` so it integrates with `fmt.Fprintf`, etc.
|
||||
|
||||
**Anti-pattern:** Allocating on every append; requiring explicit initialization;
|
||||
not implementing standard interfaces (`io.Writer`).
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// strings/builder.go:14-16
|
||||
// A Builder is used to efficiently build a string using [Builder.Write] methods.
|
||||
// It minimizes memory copying. The zero value is ready to use.
|
||||
// Do not copy a non-zero Builder.
|
||||
type Builder struct {
|
||||
addr *Builder
|
||||
buf []byte
|
||||
}
|
||||
|
||||
// strings/builder.go:92-96
|
||||
func (b *Builder) WriteString(s string) (int, error) {
|
||||
b.copyCheck()
|
||||
b.buf = append(b.buf, s...)
|
||||
return len(s), nil
|
||||
}
|
||||
|
||||
// strings/builder.go:48-50
|
||||
func (b *Builder) String() string {
|
||||
return unsafe.String(unsafe.SliceData(b.buf), len(b.buf))
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Layered API (Convenience → Full Control)
|
||||
|
||||
**Pattern name:** Convenience Wrappers over Configurable Core
|
||||
|
||||
**Source citation:** `os/file.go` lines 385–415
|
||||
|
||||
**What it does:** Simple functions (`Open`, `Create`) delegate to the fully configurable
|
||||
`OpenFile` with pre-set flags. Users choose their level of control.
|
||||
|
||||
**Why:** 90% of file opens are reads or creates. Layered APIs serve the common case
|
||||
without hiding power. The naming makes intent clear.
|
||||
|
||||
**Anti-pattern:** Only exposing the full-power version; making users learn flag
|
||||
constants for simple reads; duplicating implementation across convenience functions.
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// os/file.go:389-393
|
||||
// Open opens the named file for reading.
|
||||
func Open(name string) (*File, error) {
|
||||
return OpenFile(name, O_RDONLY, 0)
|
||||
}
|
||||
|
||||
// os/file.go:399-403
|
||||
// Create creates or truncates the named file.
|
||||
func Create(name string) (*File, error) {
|
||||
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
|
||||
}
|
||||
|
||||
// os/file.go:410+ (the general form)
|
||||
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Package-Level Functions Delegating to DefaultXxx
|
||||
|
||||
**Pattern name:** Convenience Package Functions
|
||||
|
||||
**Source citation:** `net/http/client.go` line 109, implied by `http.Get`, `http.Post`
|
||||
|
||||
**What it does:** Top-level functions like `http.Get(url)` call methods on the
|
||||
`DefaultClient`. Users can bypass by creating their own `Client`.
|
||||
|
||||
**Why:** Makes the simple case trivial (one-liner HTTP requests). No import of
|
||||
constructors or setup needed. The package "just works" for basic usage.
|
||||
|
||||
**Anti-pattern:** Not providing convenience functions (forcing explicit construction
|
||||
even for prototyping); making the default's behavior non-obvious.
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// net/http/client.go:109
|
||||
var DefaultClient = &Client{}
|
||||
|
||||
// net/http/client.go (implied pattern):
|
||||
// func Get(url string) (resp *Response, err error) {
|
||||
// return DefaultClient.Get(url)
|
||||
// }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Register Pattern (Pluggable Algorithms)
|
||||
|
||||
**Pattern name:** RegisterXxx for Side-Effect Imports
|
||||
|
||||
**Source citation:** `crypto/crypto.go` lines 145–150
|
||||
|
||||
**What it does:** A `RegisterHash(h Hash, f func() hash.Hash)` function allows
|
||||
algorithm implementations in sub-packages to register themselves via `init()`.
|
||||
The main package dispatches based on the registered factories.
|
||||
|
||||
**Why:** Decouples the algorithm registry from specific implementations. Users import
|
||||
only the algorithms they need (e.g., `_ "crypto/sha256"`). Reduces binary size and
|
||||
avoids circular dependencies.
|
||||
|
||||
**Anti-pattern:** Hard-coding all implementations; requiring explicit constructor calls
|
||||
for each algorithm; using global mutable state without clear ownership.
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// crypto/crypto.go:145-150
|
||||
func RegisterHash(h Hash, f func() hash.Hash) {
|
||||
if h == 0 || h >= maxHash {
|
||||
panic("crypto: RegisterHash of unknown hash function")
|
||||
}
|
||||
hashes[h] = f
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Graceful Shutdown Pattern
|
||||
|
||||
**Pattern name:** Close vs Shutdown (Immediate vs Graceful)
|
||||
|
||||
**Source citation:** `net/http/server.go` lines 3171–3220 (Close), 3221+ (Shutdown)
|
||||
|
||||
**What it does:** Provides both `Close()` (immediate, forceful) and `Shutdown(ctx)`
|
||||
(graceful, waits for in-flight requests). The context on Shutdown provides a
|
||||
timeout mechanism.
|
||||
|
||||
**Why:** Different operational scenarios need different termination semantics.
|
||||
Graceful shutdown is critical for production services; immediate close is needed for
|
||||
tests and emergency stops.
|
||||
|
||||
**Anti-pattern:** Only providing one shutdown mode; not accepting a context for
|
||||
timeout control; leaking goroutines on shutdown.
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// net/http/server.go:3171-3175
|
||||
func (s *Server) Close() error {
|
||||
s.inShutdown.Store(true)
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
err := s.closeListenersLocked()
|
||||
// ... forcefully closes all active connections
|
||||
}
|
||||
|
||||
// net/http/server.go:3221+
|
||||
// Shutdown gracefully shuts down the server without interrupting any
|
||||
// active connections.
|
||||
func (s *Server) Shutdown(ctx context.Context) error {
|
||||
s.inShutdown.Store(true)
|
||||
// ... closes listeners, waits for idle, respects ctx deadline
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Channel-Based Timer/Ticker API
|
||||
|
||||
**Pattern name:** NewXxx Returning Channel-Bearing Struct
|
||||
|
||||
**Source citation:** `time/tick.go` lines 16–45, `time/sleep.go` lines 89–155
|
||||
|
||||
**What it does:** `NewTicker(d)` and `NewTimer(d)` return structs with a `C <-chan Time`
|
||||
field. Consumers select on the channel to receive time events.
|
||||
|
||||
**Why:** Integrates time-based events with Go's concurrency primitives (select).
|
||||
The channel-based API composes naturally with other goroutine patterns.
|
||||
|
||||
**Anti-pattern:** Callback-based timer APIs that don't compose with select; exposing
|
||||
the send side of the channel; not documenting goroutine safety.
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// time/tick.go:16-18
|
||||
type Ticker struct {
|
||||
C <-chan Time // The channel on which the ticks are delivered.
|
||||
initTicker bool
|
||||
}
|
||||
|
||||
// time/tick.go:36-45
|
||||
func NewTicker(d Duration) *Ticker {
|
||||
if d <= 0 {
|
||||
panic("non-positive interval for NewTicker")
|
||||
}
|
||||
c := make(chan Time, 1)
|
||||
t := (*Ticker)(unsafe.Pointer(newTimer(when(d), int64(d), sendTime, c, syncTimer(c))))
|
||||
t.C = c
|
||||
return t
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,342 @@
|
||||
# Documentation Patterns in the Go Standard Library
|
||||
|
||||
## 1. Package Documentation (doc.go or Package Comment)
|
||||
|
||||
**Pattern name:** Package Doc Comment
|
||||
|
||||
**Source citation:** `net/http/doc.go` lines 6–30, `os/file.go` lines 5–43, `log/slog/doc.go` lines 6–30
|
||||
|
||||
**What it does:** The first file in a package (by convention `doc.go`, or the main
|
||||
source file) starts with a `// Package xxx ...` comment that explains the package's
|
||||
purpose, key types, and typical usage patterns.
|
||||
|
||||
**Why:** This is the first thing users see in `go doc <pkg>` and on pkg.go.dev. It
|
||||
sets context, teaches the mental model, and provides copy-paste examples.
|
||||
|
||||
**Anti-pattern:** No package comment; package comment that just restates the package
|
||||
name ("Package http provides http"); putting documentation in README instead of code.
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// net/http/doc.go:6-12
|
||||
/*
|
||||
Package http provides HTTP client and server implementations.
|
||||
|
||||
[Get], [Head], [Post], and [PostForm] make HTTP (or HTTPS) requests:
|
||||
|
||||
resp, err := http.Get("http://example.com/")
|
||||
...
|
||||
*/
|
||||
```
|
||||
|
||||
```go
|
||||
// os/file.go:5-43
|
||||
// Package os provides a platform-independent interface to operating system
|
||||
// functionality. The design is Unix-like, although the error handling is
|
||||
// Go-like; failing calls return values of type error rather than error numbers.
|
||||
// Often, more information is available within the error. For example,
|
||||
// if a call that takes a file name fails, such as [Open] or [Stat], the error
|
||||
// will include the failing file name when printed and will be of type
|
||||
// [*PathError], which may be unpacked for more information.
|
||||
```
|
||||
|
||||
```go
|
||||
// log/slog/doc.go:6-10
|
||||
/*
|
||||
Package slog provides structured logging,
|
||||
in which log records include a message,
|
||||
a severity level, and various other attributes
|
||||
expressed as key-value pairs.
|
||||
*/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Section Headers in Package Docs
|
||||
|
||||
**Pattern name:** `# Heading` in Doc Comments
|
||||
|
||||
**Source citation:** `os/file.go` lines 37–43, `net/http/doc.go` (multiple sections)
|
||||
|
||||
**What it does:** Uses `# Section Name` within the package doc comment to organize
|
||||
long documentation into navigable sections.
|
||||
|
||||
**Why:** Large packages need structure. Section headers render as links in pkg.go.dev
|
||||
and provide a scannable table of contents.
|
||||
|
||||
**Anti-pattern:** Wall-of-text package docs; using `===` or `---` (not recognized);
|
||||
too many sections (fragmenting simple docs).
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// os/file.go:37
|
||||
// # Concurrency
|
||||
//
|
||||
// The methods of [File] correspond to file system operations. All are
|
||||
// safe for concurrent use.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Type/Function Comment Convention
|
||||
|
||||
**Pattern name:** `// TypeName verb...` or `// FuncName verb...`
|
||||
|
||||
**Source citation:** `net/http/server.go` lines 64–82 (Handler), `bufio/scan.go` lines 14–27 (Scanner)
|
||||
|
||||
**What it does:** Every exported identifier's doc comment starts with the identifier
|
||||
name, followed by a verb phrase describing what it does or represents.
|
||||
|
||||
**Why:** `go doc` extracts the first sentence as a summary. Starting with the name
|
||||
ensures it reads correctly in both isolation (summary lists) and full context.
|
||||
This is enforced by convention and checked by linters.
|
||||
|
||||
**Anti-pattern:** Starting with "This function..." or "The Foo type..."; starting
|
||||
with articles ("A Handler is...") for functions (acceptable for types); omitting
|
||||
the comment entirely.
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// net/http/server.go:64
|
||||
// A Handler responds to an HTTP request.
|
||||
|
||||
// bufio/scan.go:14-17
|
||||
// Scanner provides a convenient interface for reading data such as
|
||||
// a file of newline-delimited lines of text.
|
||||
|
||||
// net/http/request.go:867
|
||||
// NewRequest wraps NewRequestWithContext using context.Background.
|
||||
|
||||
// os/file.go:389-390
|
||||
// Open opens the named file for reading.
|
||||
|
||||
// regexp/regexp.go:310-312
|
||||
// MustCompile is like [Compile] but panics if the expression cannot be parsed.
|
||||
// It simplifies safe initialization of global variables holding compiled regular
|
||||
// expressions.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Doc Links (Square Bracket References)
|
||||
|
||||
**Pattern name:** `[TypeName]`, `[Package.Symbol]`, `[Method]` Links
|
||||
|
||||
**Source citation:** `net/http/server.go` lines 65–70, `os/file.go` line 9
|
||||
|
||||
**What it does:** Doc comments use `[SymbolName]` to create hyperlinks to other
|
||||
identifiers. These render as clickable links on pkg.go.dev.
|
||||
|
||||
**Why:** Cross-references help users navigate the API. Links are concise and
|
||||
don't clutter the plain-text rendering.
|
||||
|
||||
**Anti-pattern:** Using full URLs to godoc pages; not linking related types;
|
||||
over-linking (every mention of every type).
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// net/http/server.go:65-70
|
||||
// [Handler.ServeHTTP] should write reply headers and data to the [ResponseWriter]
|
||||
// and then return. Returning signals that the request is finished; it
|
||||
// is not valid to use the [ResponseWriter] or read from the
|
||||
// [Request.Body] after or concurrently with the completion of the
|
||||
// ServeHTTP call.
|
||||
|
||||
// os/file.go:9-11
|
||||
// if a call that takes a file name fails, such as [Open] or [Stat], the error
|
||||
// will include the failing file name when printed and will be of type
|
||||
// [*PathError], which may be unpacked for more information.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Example Test Functions
|
||||
|
||||
**Pattern name:** `func ExampleXxx()` / `func ExampleType_Method()`
|
||||
|
||||
**Source citation:** `regexp/example_test.go` lines 13–28, `net/http/example_handle_test.go` lines 16–31
|
||||
|
||||
**What it does:** Functions named `Example`, `ExampleXxx`, or `ExampleType_Method`
|
||||
in `_test.go` files serve as both executable tests and documentation. They include
|
||||
an `// Output:` comment that `go test` verifies.
|
||||
|
||||
**Why:** Examples that compile, run, and are verified can never go stale. They appear
|
||||
in `go doc` and pkg.go.dev alongside the relevant symbol. They teach by showing
|
||||
real, working code.
|
||||
|
||||
**Anti-pattern:** Examples that don't compile; examples without Output comments
|
||||
(not verified); examples in README that drift from reality.
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// regexp/example_test.go:13-28
|
||||
func Example() {
|
||||
// Compile the expression once, usually at init time.
|
||||
// Use raw strings to avoid having to quote the backslashes.
|
||||
var validID = regexp.MustCompile(`^[a-z]+\[[0-9]+\]$`)
|
||||
|
||||
fmt.Println(validID.MatchString("adam[23]"))
|
||||
fmt.Println(validID.MatchString("eve[7]"))
|
||||
fmt.Println(validID.MatchString("Job[48]"))
|
||||
fmt.Println(validID.MatchString("snakey"))
|
||||
// Output:
|
||||
// true
|
||||
// true
|
||||
// false
|
||||
// false
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// net/http/example_handle_test.go:16-31
|
||||
type countHandler struct {
|
||||
mu sync.Mutex // guards n
|
||||
n int
|
||||
}
|
||||
|
||||
func (h *countHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
h.n++
|
||||
fmt.Fprintf(w, "count is %d\n", h.n)
|
||||
}
|
||||
|
||||
func ExampleHandle() {
|
||||
http.Handle("/count", new(countHandler))
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Inline Code Examples in Doc Comments
|
||||
|
||||
**Pattern name:** Indented Code Blocks in Comments
|
||||
|
||||
**Source citation:** `os/file.go` lines 17–35, `time/time.go` lines 928–933
|
||||
|
||||
**What it does:** Doc comments include indented code snippets (4 spaces) that render
|
||||
as preformatted code blocks in godoc.
|
||||
|
||||
**Why:** Shows typical usage patterns directly in the doc comment without requiring
|
||||
a separate Example test function. Good for short, illustrative snippets.
|
||||
|
||||
**Anti-pattern:** Non-indented code that doesn't render as code; examples too long
|
||||
for inline (use Example functions instead); examples that reference unexported symbols.
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// os/file.go:17-21
|
||||
// Here is a simple example, opening a file and reading some of it.
|
||||
//
|
||||
// file, err := os.Open("file.go") // For read access.
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
|
||||
// time/time.go:928-933
|
||||
// To count the number of units in a [Duration], divide:
|
||||
//
|
||||
// second := time.Second
|
||||
// fmt.Print(int64(second/time.Millisecond)) // prints 1000
|
||||
//
|
||||
// To convert an integer number of units to a Duration, multiply:
|
||||
//
|
||||
// seconds := 10
|
||||
// fmt.Print(time.Duration(seconds)*time.Second) // prints 10s
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Deprecated Annotations
|
||||
|
||||
**Pattern name:** `// Deprecated: ...` in Doc Comments
|
||||
|
||||
**Source citation:** `net/http/server.go` line 57 (ErrWriteAfterFlush), `os/file.go` lines 93–95
|
||||
|
||||
**What it does:** A paragraph starting with `Deprecated:` marks an identifier as
|
||||
deprecated and explains what to use instead.
|
||||
|
||||
**Why:** Recognized by tooling (go vet, staticcheck, IDEs). Provides a migration
|
||||
path without breaking backward compatibility.
|
||||
|
||||
**Anti-pattern:** Removing deprecated APIs (breaks semver); deprecating without
|
||||
suggesting an alternative; using non-standard deprecation markers.
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// net/http/server.go:55-57
|
||||
// Deprecated: ErrWriteAfterFlush is no longer returned by
|
||||
// anything in the net/http package. Callers should not
|
||||
// compare errors against this variable.
|
||||
ErrWriteAfterFlush = errors.New("unused")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Error Documentation Convention
|
||||
|
||||
**Pattern name:** "If there is an error, it will be of type [*XxxError]"
|
||||
|
||||
**Source citation:** `os/file.go` lines 388, 406
|
||||
|
||||
**What it does:** Functions document the concrete error type they return, enabling
|
||||
callers to type-assert for additional context.
|
||||
|
||||
**Why:** Go's error handling relies on type assertions and `errors.Is/As`. Knowing
|
||||
the concrete type lets callers extract structured information (path, operation,
|
||||
underlying cause).
|
||||
|
||||
**Anti-pattern:** Returning opaque errors with no documented structure; returning
|
||||
different error types from the same function without documenting which.
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// os/file.go:388-390
|
||||
// Open opens the named file for reading. If successful, methods on
|
||||
// the returned file can be used for reading; the associated file
|
||||
// descriptor has mode [O_RDONLY].
|
||||
// If there is an error, it will be of type [*PathError].
|
||||
func Open(name string) (*File, error) {
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Concurrency Documentation
|
||||
|
||||
**Pattern name:** "Safe for concurrent use" / Concurrency Guarantees
|
||||
|
||||
**Source citation:** `net/http/transport.go` lines 79–80, `os/types.go` line 17, `regexp/regexp.go` lines 77–79
|
||||
|
||||
**What it does:** Doc comments explicitly state the concurrency safety of a type
|
||||
or note exceptions where concurrent use is not safe.
|
||||
|
||||
**Why:** Go programs are inherently concurrent. Without explicit documentation,
|
||||
users must guess whether a type needs external synchronization.
|
||||
|
||||
**Anti-pattern:** Leaving concurrency safety undocumented; documenting it
|
||||
inconsistently across methods; saying "thread-safe" (Java-ism, use "safe for
|
||||
concurrent use by multiple goroutines").
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// net/http/transport.go:79-80
|
||||
// Transports should be reused instead of created as needed.
|
||||
// Transports are safe for concurrent use by multiple goroutines.
|
||||
|
||||
// os/types.go:17
|
||||
// The methods of File are safe for concurrent use.
|
||||
|
||||
// regexp/regexp.go:77-79
|
||||
// A Regexp is safe for concurrent use by multiple goroutines,
|
||||
// except for configuration methods, such as [Regexp.Longest].
|
||||
```
|
||||
@@ -0,0 +1,390 @@
|
||||
# Go Interface Design Patterns
|
||||
|
||||
Patterns extracted from the Go standard library source code.
|
||||
|
||||
---
|
||||
|
||||
## 1. Small Interfaces (1-2 Methods)
|
||||
|
||||
### Source: `src/io/io.go:80-92` (Reader), `93-103` (Writer), `105-109` (Closer)
|
||||
|
||||
Go's most powerful interfaces have exactly **one method**:
|
||||
|
||||
```go
|
||||
// src/io/io.go:80-92
|
||||
type Reader interface {
|
||||
Read(p []byte) (n int, err error)
|
||||
}
|
||||
|
||||
// src/io/io.go:93-103
|
||||
type Writer interface {
|
||||
Write(p []byte) (n int, err error)
|
||||
}
|
||||
|
||||
// src/io/io.go:105-109
|
||||
type Closer interface {
|
||||
Close() error
|
||||
}
|
||||
```
|
||||
|
||||
### Why
|
||||
|
||||
Small interfaces are easy to implement and easy to compose. Any type can satisfy `io.Reader` by implementing a single method. This maximizes the number of types that can participate in the ecosystem — files, network connections, buffers, compressors, encryptors all satisfy `Reader`.
|
||||
|
||||
### Anti-pattern
|
||||
|
||||
```go
|
||||
// DON'T: Giant interface that tries to cover everything
|
||||
type FileSystem interface {
|
||||
Open(name string) (File, error)
|
||||
Create(name string) (File, error)
|
||||
Remove(name string) error
|
||||
Stat(name string) (FileInfo, error)
|
||||
ReadDir(name string) ([]DirEntry, error)
|
||||
MkdirAll(path string, perm FileMode) error
|
||||
// ... 10 more methods
|
||||
}
|
||||
```
|
||||
|
||||
Large interfaces are hard to implement, hard to mock, and couple consumers to capabilities they don't need.
|
||||
|
||||
---
|
||||
|
||||
## 2. Interface Composition
|
||||
|
||||
### Source: `src/io/io.go:131-155`
|
||||
|
||||
Compose small interfaces into larger ones only when needed:
|
||||
|
||||
```go
|
||||
// src/io/io.go:131-134
|
||||
type ReadWriter interface {
|
||||
Reader
|
||||
Writer
|
||||
}
|
||||
|
||||
// src/io/io.go:136-139
|
||||
type ReadCloser interface {
|
||||
Reader
|
||||
Closer
|
||||
}
|
||||
|
||||
// src/io/io.go:141-144
|
||||
type WriteCloser interface {
|
||||
Writer
|
||||
Closer
|
||||
}
|
||||
|
||||
// src/io/io.go:146-150
|
||||
type ReadWriteCloser interface {
|
||||
Reader
|
||||
Writer
|
||||
Closer
|
||||
}
|
||||
```
|
||||
|
||||
### Why
|
||||
|
||||
Composition lets you express precisely what you need. A function that only reads should accept `Reader`, not `ReadWriteCloser`. Callers provide the minimum; composers build up.
|
||||
|
||||
### Anti-pattern
|
||||
|
||||
```go
|
||||
// DON'T: Define a big interface, then use only part of it
|
||||
func processData(rw ReadWriteCloser) {
|
||||
// only calls Read... why require Write and Close?
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Accept Interfaces, Return Structs
|
||||
|
||||
### Source: `src/io/io.go:461` (LimitReader), `src/io/io.go:618` (TeeReader)
|
||||
|
||||
```go
|
||||
// src/io/io.go:461
|
||||
func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }
|
||||
|
||||
// src/io/io.go:467-471
|
||||
type LimitedReader struct {
|
||||
R Reader // underlying reader
|
||||
N int64 // max bytes remaining
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// src/io/io.go:618-620
|
||||
func TeeReader(r Reader, w Writer) Reader {
|
||||
return &teeReader{r, w}
|
||||
}
|
||||
```
|
||||
|
||||
### Why
|
||||
|
||||
- **Accept interfaces**: Maximizes what callers can pass in — any `Reader` works.
|
||||
- **Return structs** (or concrete types): Gives callers access to the full type, including fields and methods not in any interface. `LimitedReader` exposes `R` and `N` publicly so callers can inspect remaining bytes.
|
||||
|
||||
The return type of `LimitReader` is `Reader` (interface), but the underlying value is `*LimitedReader` (struct). Functions like `io.Copy` can type-assert to `*LimitedReader` to optimize buffer sizes (line 425).
|
||||
|
||||
### Anti-pattern
|
||||
|
||||
```go
|
||||
// DON'T: Accept concrete types
|
||||
func LimitReader(r *os.File, n int64) Reader // too restrictive
|
||||
|
||||
// DON'T: Return interfaces when you have useful struct fields
|
||||
func NewServer() ServerInterface // hides useful config fields
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Interface Satisfaction as a Compile-Time Check
|
||||
|
||||
### Source: `src/io/io.go:645`, `src/net/http/server.go:4071`
|
||||
|
||||
```go
|
||||
// src/io/io.go:645
|
||||
var _ ReaderFrom = discard{}
|
||||
|
||||
// src/net/http/server.go:4071
|
||||
var _ Pusher = (*timeoutWriter)(nil)
|
||||
```
|
||||
|
||||
### Why
|
||||
|
||||
These blank-identifier declarations cause a compile error if the type stops implementing the interface. They're documentation and safety net combined — no runtime cost.
|
||||
|
||||
### Anti-pattern
|
||||
|
||||
```go
|
||||
// DON'T: Discover interface failures at runtime
|
||||
func doSomething(w ResponseWriter) {
|
||||
pusher := w.(Pusher) // panics if w doesn't implement Pusher
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Interface-Based Polymorphism (sort.Interface)
|
||||
|
||||
### Source: `src/sort/sort.go:16-41`
|
||||
|
||||
```go
|
||||
// src/sort/sort.go:16-41
|
||||
type Interface interface {
|
||||
Len() int
|
||||
Less(i, j int) bool
|
||||
Swap(i, j int)
|
||||
}
|
||||
```
|
||||
|
||||
### Why
|
||||
|
||||
The sort package defines *behavior* it needs, not data it operates on. Any collection — slices, linked lists, database result sets — can be sorted by implementing three methods. The algorithm is decoupled from the data structure.
|
||||
|
||||
### Anti-pattern
|
||||
|
||||
```go
|
||||
// DON'T: Hardcode to a specific data type
|
||||
func Sort(data []int) // only works for []int
|
||||
func Sort(data []interface{}) // loses type safety
|
||||
```
|
||||
|
||||
Note: Since Go 1.21, `slices.SortFunc` is preferred for slices (generic + faster), but `sort.Interface` remains the pattern for non-slice collections.
|
||||
|
||||
---
|
||||
|
||||
## 6. The Adapter Pattern (HandlerFunc)
|
||||
|
||||
### Source: `src/net/http/server.go:2334-2342`
|
||||
|
||||
```go
|
||||
// src/net/http/server.go:2334-2338
|
||||
// The HandlerFunc type is an adapter to allow the use of
|
||||
// ordinary functions as HTTP handlers. If f is a function
|
||||
// with the appropriate signature, HandlerFunc(f) is a
|
||||
// Handler that calls f.
|
||||
type HandlerFunc func(ResponseWriter, *Request)
|
||||
|
||||
// src/net/http/server.go:2341-2342
|
||||
// ServeHTTP calls f(w, r).
|
||||
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
|
||||
f(w, r)
|
||||
}
|
||||
```
|
||||
|
||||
### Why
|
||||
|
||||
This bridges functions and interfaces. Any function with the right signature becomes a `Handler` via `HandlerFunc(myFunc)`. You get the simplicity of functions with the composability of interfaces.
|
||||
|
||||
### Anti-pattern
|
||||
|
||||
```go
|
||||
// DON'T: Force users to create a struct just to implement an interface
|
||||
type myHandler struct{}
|
||||
func (h myHandler) ServeHTTP(w ResponseWriter, r *Request) {
|
||||
// just calls a function anyway
|
||||
handleHome(w, r)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Optional Interfaces (Runtime Feature Detection)
|
||||
|
||||
### Source: `src/net/http/server.go:165-175` (Flusher), `src/net/http/server.go:183-206` (Hijacker)
|
||||
|
||||
```go
|
||||
// src/net/http/server.go:165-170
|
||||
type Flusher interface {
|
||||
Flush()
|
||||
}
|
||||
|
||||
// src/net/http/server.go:183-206
|
||||
type Hijacker interface {
|
||||
Hijack() (net.Conn, *bufio.ReadWriter, error)
|
||||
}
|
||||
```
|
||||
|
||||
Usage pattern (from doc comments):
|
||||
```go
|
||||
// Handlers should always test for this ability at runtime.
|
||||
if flusher, ok := w.(Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
```
|
||||
|
||||
### Why
|
||||
|
||||
Not every `ResponseWriter` supports flushing or hijacking (HTTP/2 doesn't support Hijacker). Instead of bloating the main interface, optional capabilities are separate interfaces checked at runtime. This keeps the core interface small while allowing progressive enhancement.
|
||||
|
||||
### Anti-pattern
|
||||
|
||||
```go
|
||||
// DON'T: Put optional capabilities in the main interface
|
||||
type ResponseWriter interface {
|
||||
Write([]byte) (int, error)
|
||||
WriteHeader(int)
|
||||
Header() Header
|
||||
Flush() // not always supported!
|
||||
Hijack() (net.Conn, *bufio.ReadWriter, error) // not always supported!
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. The Stringer Interface (Convention-Based Behavior)
|
||||
|
||||
### Source: `src/fmt/print.go:63-66`
|
||||
|
||||
```go
|
||||
// src/fmt/print.go:63-66
|
||||
type Stringer interface {
|
||||
String() string
|
||||
}
|
||||
```
|
||||
|
||||
### Why
|
||||
|
||||
`fmt` checks if a value implements `Stringer` and calls `String()` for its text representation. This is a *convention*: any type in any package can participate without importing `fmt`. The interface acts as a protocol.
|
||||
|
||||
Similarly, `GoStringer` (line 71) controls `%#v` output, and `Formatter` (line 58-61) gives total control over formatting.
|
||||
|
||||
### Anti-pattern
|
||||
|
||||
```go
|
||||
// DON'T: Use type switches over known types
|
||||
func printThing(v any) string {
|
||||
switch v := v.(type) {
|
||||
case MyType: return v.name
|
||||
case OtherType: return v.label
|
||||
// ... must know about every type
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Interface Upgrade Pattern (WriterTo/ReaderFrom in io.Copy)
|
||||
|
||||
### Source: `src/io/io.go:410-417`
|
||||
|
||||
```go
|
||||
// src/io/io.go:410-417
|
||||
func copyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) {
|
||||
// If the reader has a WriteTo method, use it to do the copy.
|
||||
// Avoids an allocation and a copy.
|
||||
if wt, ok := src.(WriterTo); ok {
|
||||
return wt.WriteTo(dst)
|
||||
}
|
||||
// Similarly, if the writer has a ReadFrom method, use it to do the copy.
|
||||
if rf, ok := dst.(ReaderFrom); ok {
|
||||
return rf.ReadFrom(src)
|
||||
}
|
||||
// ... fallback to buffer-based copy
|
||||
}
|
||||
```
|
||||
|
||||
### Why
|
||||
|
||||
The core function works with the minimum interface (`Reader`/`Writer`) but *upgrades* behavior when richer interfaces are available. `*os.File` implements `ReadFrom` using `sendfile(2)` — zero-copy kernel transfer. The generic path works, but specialized implementations optimize transparently.
|
||||
|
||||
### Anti-pattern
|
||||
|
||||
```go
|
||||
// DON'T: Always use the slow generic path
|
||||
func Copy(dst Writer, src Reader) {
|
||||
buf := make([]byte, 32*1024)
|
||||
// always allocates, never leverages kernel optimizations
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. The driver.Driver Pattern (Plugin Interfaces)
|
||||
|
||||
### Source: `src/database/sql/driver/driver.go:85-97`, `104-112`
|
||||
|
||||
```go
|
||||
// src/database/sql/driver/driver.go:85-97
|
||||
type Driver interface {
|
||||
Open(name string) (Conn, error)
|
||||
}
|
||||
|
||||
// src/database/sql/driver/driver.go:104-112
|
||||
type DriverContext interface {
|
||||
OpenConnector(name string) (Connector, error)
|
||||
}
|
||||
```
|
||||
|
||||
### Why
|
||||
|
||||
The `database/sql` package defines interfaces that driver authors implement. The base interface (`Driver`) is minimal (one method). Extended capabilities (`DriverContext`, `Pinger`, `Execer`, `Queryer`) are opt-in — the `sql` package checks for them at runtime. This allows the ecosystem to grow without breaking existing drivers.
|
||||
|
||||
### Anti-pattern
|
||||
|
||||
```go
|
||||
// DON'T: One massive interface all drivers must implement
|
||||
type Driver interface {
|
||||
Open(name string) (Conn, error)
|
||||
OpenConnector(name string) (Connector, error)
|
||||
Ping(ctx context.Context) error
|
||||
// ... every driver must implement everything, even if not supported
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary: Go Interface Design Principles
|
||||
|
||||
| Principle | Standard Library Example |
|
||||
|-----------|------------------------|
|
||||
| Keep interfaces small (1-2 methods) | `io.Reader`, `io.Writer`, `fmt.Stringer` |
|
||||
| Compose small interfaces | `io.ReadWriteCloser` |
|
||||
| Accept interfaces, return structs | `io.LimitReader`, `io.TeeReader` |
|
||||
| Use adapter types for functions | `http.HandlerFunc` |
|
||||
| Optional capabilities via separate interfaces | `http.Flusher`, `http.Hijacker` |
|
||||
| Compile-time interface checks | `var _ Interface = (*Type)(nil)` |
|
||||
| Runtime interface upgrade for optimization | `io.Copy` → `WriterTo`/`ReaderFrom` |
|
||||
| Plugin/driver interfaces start minimal | `database/sql/driver.Driver` |
|
||||
@@ -0,0 +1,404 @@
|
||||
# Struct Design Patterns in the Go Standard Library
|
||||
|
||||
## 1. Zero-Value Usability
|
||||
|
||||
**Pattern name:** Zero Value Ready
|
||||
|
||||
**Source citation:** `net/http/client.go` lines 31–35, `strings/builder.go` lines 14–16
|
||||
|
||||
**What it does:** Structs are designed so their zero value is immediately useful without
|
||||
explicit initialization. Nil fields fall back to sensible defaults at method call time.
|
||||
|
||||
**Why:** Eliminates mandatory constructors, reduces boilerplate, makes the type
|
||||
self-documenting about its defaults. Users can write `var c http.Client` and start
|
||||
making requests.
|
||||
|
||||
**Anti-pattern:** Requiring a constructor for basic use; panicking on zero-value use;
|
||||
requiring all fields be set before the type is functional.
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// net/http/client.go:31-35
|
||||
// A Client is an HTTP client. Its zero value ([DefaultClient]) is a
|
||||
// usable client that uses [DefaultTransport].
|
||||
type Client struct {
|
||||
Transport RoundTripper // If nil, DefaultTransport is used.
|
||||
// ...
|
||||
}
|
||||
|
||||
// net/http/client.go:109
|
||||
var DefaultClient = &Client{}
|
||||
```
|
||||
|
||||
```go
|
||||
// strings/builder.go:14-16
|
||||
// A Builder is used to efficiently build a string using [Builder.Write] methods.
|
||||
// It minimizes memory copying. The zero value is ready to use.
|
||||
// Do not copy a non-zero Builder.
|
||||
type Builder struct {
|
||||
addr *Builder
|
||||
buf []byte
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// bytes/buffer.go:19-20
|
||||
// A Buffer is a variable-sized buffer of bytes with [Buffer.Read] and [Buffer.Write] methods.
|
||||
// The zero value for Buffer is an empty buffer ready to use.
|
||||
type Buffer struct {
|
||||
buf []byte
|
||||
off int
|
||||
lastRead readOp
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Unexported Struct with Exported Wrapper
|
||||
|
||||
**Pattern name:** Indirection via Unexported Impl
|
||||
|
||||
**Source citation:** `os/types.go` lines 16–20, `os/file_unix.go` lines 59–71
|
||||
|
||||
**What it does:** The exported type (`File`) embeds a pointer to an unexported type
|
||||
(`*file`) that holds the real implementation state. Users interact only with the
|
||||
exported wrapper.
|
||||
|
||||
**Why:** Prevents users from directly constructing or copying the implementation struct.
|
||||
Allows platform-specific implementations behind a uniform exported API. The extra
|
||||
indirection ensures finalizers close the correct descriptor.
|
||||
|
||||
**Anti-pattern:** Exporting all implementation fields; allowing users to construct
|
||||
the struct via a literal (bypassing invariants); needing platform #ifdefs in the
|
||||
public API.
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// os/types.go:16-20
|
||||
// File represents an open file descriptor.
|
||||
//
|
||||
// The methods of File are safe for concurrent use.
|
||||
type File struct {
|
||||
*file // os specific
|
||||
}
|
||||
|
||||
// os/file_unix.go:59-71
|
||||
// file is the real representation of *File.
|
||||
// The extra level of indirection ensures that no clients of os
|
||||
// can overwrite this data, which could cause the finalizer
|
||||
// to close the wrong file descriptor.
|
||||
type file struct {
|
||||
pfd poll.FD
|
||||
name string
|
||||
dirinfo atomic.Pointer[dirInfo]
|
||||
nonblock bool
|
||||
stdoutOrErr bool
|
||||
appendMode bool
|
||||
inRoot bool
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Constructor Functions (NewXxx)
|
||||
|
||||
**Pattern name:** NewXxx Constructor
|
||||
|
||||
**Source citation:** `bufio/scan.go` lines 89–96, `bufio/bufio.go` lines 50–60
|
||||
|
||||
**What it does:** A package-level function `NewXxx(deps) *Xxx` constructs the type
|
||||
with required dependencies and internal defaults that can't be expressed via zero
|
||||
value alone.
|
||||
|
||||
**Why:** When a type has mandatory dependencies (e.g., an `io.Reader`), a constructor
|
||||
clearly communicates what's required. The constructor can set internal invariants
|
||||
(buffer sizes, split functions) that users shouldn't need to know about.
|
||||
|
||||
**Anti-pattern:** Forcing users to manually set unexported fields; having a constructor
|
||||
that takes 10 optional parameters (use config struct instead); requiring New when
|
||||
zero value would suffice.
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// bufio/scan.go:89-96
|
||||
func NewScanner(r io.Reader) *Scanner {
|
||||
return &Scanner{
|
||||
r: r,
|
||||
split: ScanLines,
|
||||
maxTokenSize: MaxScanTokenSize,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// bufio/bufio.go:50-62
|
||||
func NewReaderSize(rd io.Reader, size int) *Reader {
|
||||
// Is it already a Reader?
|
||||
b, ok := rd.(*Reader)
|
||||
if ok && len(b.buf) >= size {
|
||||
return b
|
||||
}
|
||||
r := new(Reader)
|
||||
r.reset(make([]byte, max(size, minReadBufferSize)), rd)
|
||||
return r
|
||||
}
|
||||
|
||||
// NewReader returns a new [Reader] whose buffer has the default size.
|
||||
func NewReader(rd io.Reader) *Reader {
|
||||
return NewReaderSize(rd, defaultBufSize)
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// net/http/request.go:867-869
|
||||
func NewRequest(method, url string, body io.Reader) (*Request, error) {
|
||||
return NewRequestWithContext(context.Background(), method, url, body)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. NewXxx with Size/Options Variant
|
||||
|
||||
**Pattern name:** NewXxx / NewXxxSize Pair
|
||||
|
||||
**Source citation:** `bufio/bufio.go` lines 50, 62, 589, 607
|
||||
|
||||
**What it does:** Provides two constructors — one with defaults (`NewReader`) and one
|
||||
with explicit configuration (`NewReaderSize`). The default version calls the
|
||||
configurable one.
|
||||
|
||||
**Why:** Most users want the default; power users need control. Layering avoids a
|
||||
proliferation of constructor parameters for the common case.
|
||||
|
||||
**Anti-pattern:** Having only the complex constructor; making users guess the right
|
||||
buffer size; inconsistent naming (e.g., `NewReaderWithSize`).
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// bufio/bufio.go:589-607
|
||||
func NewWriterSize(w io.Writer, size int) *Writer {
|
||||
// ...
|
||||
}
|
||||
|
||||
func NewWriter(w io.Writer) *Writer {
|
||||
return NewWriterSize(w, defaultBufSize)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Config Struct Pattern
|
||||
|
||||
**Pattern name:** Configuration Struct (Exported Fields, Nil-Means-Default)
|
||||
|
||||
**Source citation:** `net/http/server.go` lines 3020–3120, `crypto/tls/common.go` lines 566+, `log/slog/handler.go` lines 135–175
|
||||
|
||||
**What it does:** A struct with exported, documented fields provides all
|
||||
configuration knobs. Nil/zero values always mean "use the default".
|
||||
|
||||
**Why:** Self-documenting via godoc; no need for a setter method per option; easy to
|
||||
construct partially; serializable; the zero value works. This is Go's primary
|
||||
configuration pattern (preferred over functional options in the stdlib).
|
||||
|
||||
**Anti-pattern:** Undocumented fields; requiring all fields set; using sentinel values
|
||||
other than zero/nil for defaults; providing setters when direct assignment works.
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// net/http/server.go:3020-3075 (abbreviated)
|
||||
type Server struct {
|
||||
Addr string // ":http" if empty
|
||||
Handler Handler // http.DefaultServeMux if nil
|
||||
TLSConfig *tls.Config // optional
|
||||
ReadTimeout time.Duration // zero means no timeout
|
||||
WriteTimeout time.Duration // zero means no timeout
|
||||
MaxHeaderBytes int // DefaultMaxHeaderBytes if zero
|
||||
ErrorLog *log.Logger // log.Default() if nil
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// log/slog/handler.go:135-175
|
||||
type HandlerOptions struct {
|
||||
AddSource bool
|
||||
Level Leveler // LevelInfo if nil
|
||||
ReplaceAttr func(groups []string, a Attr) Attr
|
||||
}
|
||||
|
||||
// Usage: If opts is nil, the default options are used.
|
||||
func NewTextHandler(w io.Writer, opts *HandlerOptions) *TextHandler {
|
||||
if opts == nil {
|
||||
opts = &HandlerOptions{}
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Interface-Based Pluggability
|
||||
|
||||
**Pattern name:** Interface Abstraction for Pluggable Implementations
|
||||
|
||||
**Source citation:** `crypto/crypto.go` lines 180–200, `net/http/transport.go` lines 66–82
|
||||
|
||||
**What it does:** Core behavior is defined via an interface. The package provides
|
||||
a default concrete implementation, but any user type satisfying the interface
|
||||
can be substituted.
|
||||
|
||||
**Why:** Decouples high-level logic from low-level implementation. Enables testing
|
||||
(mock transports), hardware integration (HSM-backed signers), and third-party
|
||||
extensions without forking the package.
|
||||
|
||||
**Anti-pattern:** Concrete-type coupling everywhere; interfaces with too many methods
|
||||
(hard to implement); accepting an interface but only ever using one implementation.
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// crypto/crypto.go:180-200
|
||||
// Signer is an interface for an opaque private key that can be used for
|
||||
// signing operations. For example, an RSA key kept in a hardware module.
|
||||
type Signer interface {
|
||||
Public() PublicKey
|
||||
Sign(rand io.Reader, digest []byte, opts SignerOpts) (signature []byte, err error)
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// net/http/transport.go (line 66+)
|
||||
// Transport is an implementation of [RoundTripper] that supports HTTP,
|
||||
// HTTPS, and HTTP proxies...
|
||||
// Transports should be reused instead of created as needed.
|
||||
// Transports are safe for concurrent use by multiple goroutines.
|
||||
|
||||
// net/http/client.go:59-60
|
||||
type Client struct {
|
||||
Transport RoundTripper // If nil, DefaultTransport is used.
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Copy Protection via Dynamic Check
|
||||
|
||||
**Pattern name:** copyCheck (Runtime Copy Detection)
|
||||
|
||||
**Source citation:** `strings/builder.go` lines 25–40
|
||||
|
||||
**What it does:** On first mutation, the Builder records its own address. Subsequent
|
||||
mutations compare the current receiver address against the recorded one. If they
|
||||
differ, the struct was copied — it panics.
|
||||
|
||||
**Why:** Go has no language-level move semantics. For types where copying after first
|
||||
use would cause data corruption or unsafe behavior (e.g., sharing an unsafe string
|
||||
buffer), a runtime check is the pragmatic solution.
|
||||
|
||||
**Anti-pattern:** Silently allowing copies that corrupt state; using `sync.Mutex`-style
|
||||
`noCopy` (vet catches it but it doesn't work for zero vs non-zero discrimination).
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// strings/builder.go:25-40
|
||||
func (b *Builder) copyCheck() {
|
||||
if b.addr == nil {
|
||||
b.addr = (*Builder)(abi.NoEscape(unsafe.Pointer(b)))
|
||||
} else if b.addr != b {
|
||||
panic("strings: illegal use of non-zero Builder copied by value")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. DefaultXxx Singleton
|
||||
|
||||
**Pattern name:** Package-Level Default Instance
|
||||
|
||||
**Source citation:** `net/http/client.go` line 109, `net/http/transport.go` lines 47–58
|
||||
|
||||
**What it does:** The package provides a pre-configured, ready-to-use instance as
|
||||
a package-level variable. Package-level convenience functions delegate to it.
|
||||
|
||||
**Why:** Makes the simple case trivial (`http.Get(url)`) while allowing custom
|
||||
instances for advanced use. Users never need to touch the defaults unless they
|
||||
have specific requirements.
|
||||
|
||||
**Anti-pattern:** Forcing construction for basic use; not providing convenience
|
||||
functions; making the default mutable in ways that affect all users.
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// net/http/client.go:108-109
|
||||
// DefaultClient is the default [Client] and is used by [Get], [Head], and [Post].
|
||||
var DefaultClient = &Client{}
|
||||
|
||||
// net/http/transport.go:47-58
|
||||
var DefaultTransport RoundTripper = &Transport{
|
||||
Proxy: ProxyFromEnvironment,
|
||||
DialContext: defaultTransportDialContext(&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}),
|
||||
ForceAttemptHTTP2: true,
|
||||
MaxIdleConns: 100,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Functional Configuration via Method Chaining (Scanner Pattern)
|
||||
|
||||
**Pattern name:** Post-Construction Configuration via Methods
|
||||
|
||||
**Source citation:** `bufio/scan.go` lines 275–293
|
||||
|
||||
**What it does:** After construction with `NewScanner`, optional configuration is
|
||||
applied via methods (`Split`, `Buffer`) before the first call to `Scan`.
|
||||
|
||||
**Why:** Keeps the constructor minimal (only the required `io.Reader`). Optional
|
||||
configuration is discoverable via methods. Panics if called after scanning starts
|
||||
(enforcing a construction → configure → use lifecycle).
|
||||
|
||||
**Anti-pattern:** Trying to pass all options into the constructor; allowing
|
||||
configuration changes mid-use that corrupt state.
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// bufio/scan.go:275-293
|
||||
// Buffer sets the initial buffer to use when scanning
|
||||
// and the maximum size of buffer that may be allocated during scanning.
|
||||
// ...
|
||||
// Buffer panics if it is called after scanning has started.
|
||||
func (s *Scanner) Buffer(buf []byte, max int) {
|
||||
if s.scanCalled {
|
||||
panic("Buffer called after Scan")
|
||||
}
|
||||
s.buf = buf
|
||||
s.maxTokenSize = max
|
||||
}
|
||||
|
||||
// Split sets the split function for the [Scanner].
|
||||
// ...
|
||||
// Split panics if it is called after scanning has started.
|
||||
func (s *Scanner) Split(split SplitFunc) {
|
||||
if s.scanCalled {
|
||||
panic("Split called after Scan")
|
||||
}
|
||||
s.split = split
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,464 @@
|
||||
# Code Style Patterns in the Go Standard Library
|
||||
|
||||
## 1. Naming Conventions: mixedCaps (No Underscores)
|
||||
|
||||
**Pattern name:** mixedCaps / MixedCaps
|
||||
|
||||
**Source citation:** All stdlib code (enforced by `gofmt` convention, documented in Effective Go)
|
||||
|
||||
**What it does:** All identifiers use mixedCaps (unexported) or MixedCaps (exported).
|
||||
Underscores are never used in Go names except for test helpers and generated code.
|
||||
|
||||
**Why:** Consistent casing makes code scannable. The exported/unexported distinction
|
||||
is communicated solely through initial capitalization — no separate `public`/`private`
|
||||
keywords needed.
|
||||
|
||||
**Anti-pattern:** `snake_case` names; `ALL_CAPS` for constants; Hungarian notation
|
||||
(`strName`, `iCount`).
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// net/http/server.go — exported
|
||||
type Server struct { ... }
|
||||
func ListenAndServe(addr string, handler Handler) error
|
||||
|
||||
// net/http/server.go — unexported
|
||||
func (s *Server) shuttingDown() bool
|
||||
const shutdownPollIntervalMax = 500 * time.Millisecond
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Acronyms Are All-Caps
|
||||
|
||||
**Pattern name:** Acronym Capitalization
|
||||
|
||||
**Source citation:** `net/http/request.go` line 130 (`URL`), `net/http/server.go` line 3041 (`TLSConfig`), `encoding/json/stream.go` line 280 (`JSON`)
|
||||
|
||||
**What it does:** Acronyms and initialisms (URL, HTTP, ID, JSON, XML, HTML, TLS, TCP)
|
||||
are always fully capitalized when exported, and fully lowercased when unexported.
|
||||
|
||||
**Why:** Consistency. `URL` not `Url`, `ID` not `Id`, `HTTP` not `Http`. This
|
||||
applies even mid-word: `ServeHTTP`, `xmlEncoder`, `htmlEscape`.
|
||||
|
||||
**Anti-pattern:** `Url`, `Http`, `Json`, `Id` — mixing cases within an acronym.
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// net/http/request.go:130
|
||||
URL *url.URL
|
||||
|
||||
// net/http/request.go:822
|
||||
func ParseHTTPVersion(vers string) (major, minor int, ok bool)
|
||||
|
||||
// net/http/server.go:3041
|
||||
TLSConfig *tls.Config
|
||||
|
||||
// encoding/json/stream.go:280
|
||||
var _ Marshaler = (*RawMessage)(nil)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. File Organization by Responsibility
|
||||
|
||||
**Pattern name:** One Concept Per File
|
||||
|
||||
**Source citation:** `net/http/` directory structure
|
||||
|
||||
**What it does:** Large packages split code into files by topic/type: `client.go`,
|
||||
`server.go`, `transport.go`, `request.go`, `response.go`, `cookie.go`, `header.go`,
|
||||
`fs.go`, `doc.go`. Each file is focused.
|
||||
|
||||
**Why:** Navigability. When you want to find client logic, you open `client.go`.
|
||||
Files stay manageable sizes. Related code lives together.
|
||||
|
||||
**Anti-pattern:** One giant file with everything; splitting by access level
|
||||
(`public.go` / `private.go`); splitting by method count rather than concept.
|
||||
|
||||
**File layout from `net/http/`:**
|
||||
|
||||
```
|
||||
client.go — Client type and methods
|
||||
transport.go — Transport type (low-level RoundTripper)
|
||||
server.go — Server, Handler, ServeMux
|
||||
request.go — Request type and parsing
|
||||
response.go — Response type and reading
|
||||
cookie.go — Cookie parsing and serialization
|
||||
header.go — Header type and canonicalization
|
||||
fs.go — FileServer, file serving
|
||||
doc.go — Package documentation
|
||||
clone.go — Clone helpers
|
||||
method.go — HTTP method constants
|
||||
pattern.go — URL pattern matching (ServeMux routing)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Blank Identifier for Interface Compliance
|
||||
|
||||
**Pattern name:** `var _ Interface = (*Type)(nil)`
|
||||
|
||||
**Source citation:** `io/io.go` line 645, `os/file.go` lines 747–750, `encoding/json/stream.go` lines 280–281
|
||||
|
||||
**What it does:** A package-level `var _ InterfaceName = (*ConcreteType)(nil)` declares
|
||||
that the concrete type must satisfy the interface. The compiler verifies this at
|
||||
build time.
|
||||
|
||||
**Why:** Catches interface drift at compile time without creating an instance. The
|
||||
blank identifier discards the value — this is purely a static assertion.
|
||||
|
||||
**Anti-pattern:** Relying on tests to catch interface conformance; skipping the check
|
||||
and discovering the mismatch at runtime; using reflection.
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// io/io.go:645
|
||||
var _ ReaderFrom = discard{}
|
||||
|
||||
// os/file.go:747-750
|
||||
var _ fs.StatFS = dirFS("")
|
||||
var _ fs.ReadFileFS = dirFS("")
|
||||
var _ fs.ReadDirFS = dirFS("")
|
||||
var _ fs.ReadLinkFS = dirFS("")
|
||||
|
||||
// encoding/json/stream.go:280-281
|
||||
var _ Marshaler = (*RawMessage)(nil)
|
||||
var _ Unmarshaler = (*RawMessage)(nil)
|
||||
|
||||
// net/http/server.go:4071
|
||||
var _ Pusher = (*timeoutWriter)(nil)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Named Return Values
|
||||
|
||||
**Pattern name:** Named Returns for Documentation (and Defer)
|
||||
|
||||
**Source citation:** `io/io.go` lines 87, 100, 314, 387; `os/file.go` lines 140, 175
|
||||
|
||||
**What it does:** Return values are given names when the names add documentary value
|
||||
(clarifying which int is what) or when `defer` needs to modify the return value.
|
||||
|
||||
**Why:** `(n int, err error)` is immediately understandable — `n` is the byte count.
|
||||
Named returns also enable `defer func() { err = wrap(err) }()` patterns.
|
||||
|
||||
**Anti-pattern:** Naming returns for trivial functions where the types are
|
||||
self-explanatory; using named returns as implicit variables throughout the function
|
||||
body (confusing naked returns); always using naked `return` statements.
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// io/io.go:87 — Interface documentation
|
||||
type Reader interface {
|
||||
Read(p []byte) (n int, err error)
|
||||
}
|
||||
|
||||
// io/io.go:100
|
||||
type Writer interface {
|
||||
Write(p []byte) (n int, err error)
|
||||
}
|
||||
|
||||
// io/io.go:387 — Named return used with defer-style logic
|
||||
func Copy(dst Writer, src Reader) (written int64, err error) {
|
||||
return copyBuffer(dst, src, nil)
|
||||
}
|
||||
|
||||
// os/file.go:140 — Named return for readability
|
||||
func (f *File) Read(b []byte) (n int, err error) {
|
||||
if err := f.checkValid("read"); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, e := f.read(b)
|
||||
return n, f.wrapErr("read", e)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Defer for Resource Cleanup
|
||||
|
||||
**Pattern name:** `defer mu.Unlock()` / `defer f.Close()`
|
||||
|
||||
**Source citation:** `net/http/server.go` lines 3173–3174, `net/http/example_handle_test.go` lines 21–22
|
||||
|
||||
**What it does:** Resources acquired at the top of a scope are immediately deferred
|
||||
for cleanup. Mutexes are locked then immediately `defer Unlock()`'d.
|
||||
|
||||
**Why:** Guarantees cleanup regardless of return path (early returns, panics). Keeps
|
||||
the acquire/release pair visually adjacent. Reduces bugs from forgotten unlocks.
|
||||
|
||||
**Anti-pattern:** Manual unlock at each return point; deferring in a loop (deferred
|
||||
calls accumulate until function exit); deferring expensive operations that should
|
||||
run earlier.
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// net/http/server.go:3173-3174
|
||||
func (s *Server) Close() error {
|
||||
s.inShutdown.Store(true)
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
// ...
|
||||
}
|
||||
|
||||
// net/http/example_handle_test.go:21-22
|
||||
func (h *countHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
h.n++
|
||||
fmt.Fprintf(w, "count is %d\n", h.n)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Error Wrapping and Sentinel Errors
|
||||
|
||||
**Pattern name:** Sentinel Errors + Structured Error Types
|
||||
|
||||
**Source citation:** `os/error.go` lines 14–27, `os/error.go` lines 46–67
|
||||
|
||||
**What it does:** Package-level sentinel errors (`ErrNotExist`, `ErrPermission`) are
|
||||
declared as `var` for use with `errors.Is()`. Structured error types (`*PathError`,
|
||||
`*SyscallError`) carry context and implement `Unwrap()` for the errors chain.
|
||||
|
||||
**Why:** Enables programmatic error handling without string matching. `errors.Is(err, os.ErrNotExist)` works regardless of wrapping depth. Structured types let callers
|
||||
extract the operation, path, or underlying syscall error.
|
||||
|
||||
**Anti-pattern:** Comparing error strings; creating unique error types for every
|
||||
possible failure; not implementing `Unwrap`; sentinel errors as `const` (breaks
|
||||
`errors.Is` for wrapped errors — use `var`).
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// os/error.go:14-27
|
||||
var (
|
||||
ErrInvalid = fs.ErrInvalid // "invalid argument"
|
||||
ErrPermission = fs.ErrPermission // "permission denied"
|
||||
ErrExist = fs.ErrExist // "file already exists"
|
||||
ErrNotExist = fs.ErrNotExist // "file does not exist"
|
||||
ErrClosed = fs.ErrClosed // "file already closed"
|
||||
)
|
||||
|
||||
// os/error.go:46
|
||||
type PathError = fs.PathError
|
||||
|
||||
// os/error.go:49-57
|
||||
type SyscallError struct {
|
||||
Syscall string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *SyscallError) Error() string { return e.Syscall + ": " + e.Err.Error() }
|
||||
func (e *SyscallError) Unwrap() error { return e.Err }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Receiver Naming: Short, Consistent, Never `this`/`self`
|
||||
|
||||
**Pattern name:** Single-Letter or Short Receiver Names
|
||||
|
||||
**Source citation:** All stdlib code; `net/http/server.go` uses `s` for Server, `bufio/scan.go` uses `s` for Scanner
|
||||
|
||||
**What it does:** Method receivers use 1–2 letter abbreviations of the type name,
|
||||
consistent across all methods of that type: `s` for `*Server`, `b` for `*Builder`,
|
||||
`f` for `*File`, `t` for `*Timer`.
|
||||
|
||||
**Why:** Receivers appear on every method. Short names reduce visual noise. Consistency
|
||||
within a type avoids confusion. `this`/`self` are alien to Go's conventions.
|
||||
|
||||
**Anti-pattern:** `this`, `self`, `me`; long receiver names like `server`, `scanner`;
|
||||
inconsistent receivers across methods of the same type.
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// net/http/server.go
|
||||
func (s *Server) ListenAndServe() error { ... }
|
||||
func (s *Server) Serve(l net.Listener) error { ... }
|
||||
func (s *Server) Shutdown(ctx context.Context) error { ... }
|
||||
|
||||
// strings/builder.go
|
||||
func (b *Builder) WriteString(s string) (int, error) { ... }
|
||||
func (b *Builder) String() string { ... }
|
||||
func (b *Builder) Grow(n int) { ... }
|
||||
|
||||
// os/file.go
|
||||
func (f *File) Read(b []byte) (n int, err error) { ... }
|
||||
func (f *File) Name() string { ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Constants: Typed, Grouped, with iota
|
||||
|
||||
**Pattern name:** Typed Constants with iota
|
||||
|
||||
**Source citation:** `crypto/crypto.go` lines 70–85, `time/time.go` lines 936–943
|
||||
|
||||
**What it does:** Related constants are grouped in a `const ( ... )` block using
|
||||
a named type and `iota` for sequential values. Constants of the same type
|
||||
are exhaustively listed together.
|
||||
|
||||
**Why:** Type safety (can't accidentally pass an `os.Flag` where a `crypto.Hash` is
|
||||
expected). `iota` eliminates magic numbers. Grouping makes the full set visible.
|
||||
|
||||
**Anti-pattern:** Untyped numeric constants; separate `const` declarations for related
|
||||
values; using raw integers in function signatures.
|
||||
|
||||
**Code examples from source:**
|
||||
|
||||
```go
|
||||
// crypto/crypto.go:70-85
|
||||
const (
|
||||
MD4 Hash = 1 + iota
|
||||
MD5
|
||||
SHA1
|
||||
SHA224
|
||||
SHA256
|
||||
// ...
|
||||
)
|
||||
|
||||
// time/time.go:936-943
|
||||
const (
|
||||
Nanosecond Duration = 1
|
||||
Microsecond = 1000 * Nanosecond
|
||||
Millisecond = 1000 * Microsecond
|
||||
Second = 1000 * Millisecond
|
||||
Minute = 60 * Second
|
||||
Hour = 60 * Minute
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Comments: Guard Clauses Over Conditions
|
||||
|
||||
**Pattern name:** `// guards x` Field Comments
|
||||
|
||||
**Source citation:** `net/http/example_handle_test.go` line 16
|
||||
|
||||
**What it does:** When a sync primitive (mutex) protects specific fields, a brief
|
||||
comment documents what it guards: `mu sync.Mutex // guards n`.
|
||||
|
||||
**Why:** Concurrency bugs come from unclear ownership. A one-line comment makes the
|
||||
lock's scope obvious to every reader.
|
||||
|
||||
**Anti-pattern:** No documentation of what a lock protects; locks that protect
|
||||
"everything" (unclear scope); comments that restate the type.
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// net/http/example_handle_test.go:16-17
|
||||
type countHandler struct {
|
||||
mu sync.Mutex // guards n
|
||||
n int
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Duration Type Pattern
|
||||
|
||||
**Pattern name:** Named Type for Semantic Units
|
||||
|
||||
**Source citation:** `time/time.go` lines 915–943
|
||||
|
||||
**What it does:** `Duration` is `type Duration int64` — a named type over a primitive.
|
||||
This gives it its own method set (`String()`, `Hours()`, `Truncate()`) and prevents
|
||||
accidental mixing with raw int64 values.
|
||||
|
||||
**Why:** Semantic meaning through the type system. You can't accidentally pass
|
||||
nanoseconds where seconds are expected. Methods provide conversion and formatting.
|
||||
Constants like `time.Second` make intent clear.
|
||||
|
||||
**Anti-pattern:** Using raw `int64` for durations; accepting `int` parameters for
|
||||
time intervals; mixing units (milliseconds in one place, seconds in another).
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// time/time.go:915
|
||||
type Duration int64
|
||||
|
||||
// time/time.go:947-949
|
||||
func (d Duration) String() string {
|
||||
var arr [32]byte
|
||||
n := d.format(&arr)
|
||||
return string(arr[n:])
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. gofmt: Non-Negotiable Formatting
|
||||
|
||||
**Pattern name:** Canonical Formatting via gofmt
|
||||
|
||||
**Source citation:** Every file in the Go standard library
|
||||
|
||||
**What it does:** All Go code is formatted with `gofmt`. Tabs for indentation, spaces
|
||||
for alignment. No style debates — the tool decides.
|
||||
|
||||
**Why:** Eliminates formatting bikesheds. All Go code looks the same regardless of
|
||||
author. Diffs show only semantic changes, never style changes. Tooling can parse
|
||||
and emit canonical code.
|
||||
|
||||
**Anti-pattern:** Manual formatting; spaces for indentation; custom alignment rules;
|
||||
checking in code that `gofmt` would modify.
|
||||
|
||||
**Key rules enforced by gofmt:**
|
||||
|
||||
- Tabs for indentation
|
||||
- Opening brace on the same line (`if x {`)
|
||||
- No optional parentheses (`if x`, not `if (x)`)
|
||||
- Aligned struct field tags
|
||||
- One blank line between top-level declarations
|
||||
- No trailing whitespace
|
||||
|
||||
---
|
||||
|
||||
## 13. Import Organization
|
||||
|
||||
**Pattern name:** Grouped Imports (stdlib / external / internal)
|
||||
|
||||
**Source citation:** `net/http/server.go` lines 8–36
|
||||
|
||||
**What it does:** Imports are organized in groups separated by blank lines:
|
||||
1. Standard library
|
||||
2. External packages (golang.org/x, third-party)
|
||||
3. Internal packages
|
||||
|
||||
The `goimports` tool enforces this automatically.
|
||||
|
||||
**Why:** Scannable at a glance. Makes dependency provenance clear (stdlib vs.
|
||||
external). Reduces merge conflicts.
|
||||
|
||||
**Code example from source:**
|
||||
|
||||
```go
|
||||
// net/http/server.go:8-36
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
// ... more stdlib ...
|
||||
"time"
|
||||
_ "unsafe" // for linkname
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
```
|
||||
Reference in New Issue
Block a user