brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
go-coding-standardsGo coding standards and style conventions grounded in Effective Go, Go Code Review Comments, and production-proven idioms. Use when writing or reviewing Go code, enforcing naming conventions, import ordering, variable declarations, struct initialization, or formatting rules. Trigger examples: "check Go style", "fix formatting", "review naming", "Go conventi…
Independent GitHub snapshot · Automated classification · Original documentation governs
Idiomatic Go conventions grounded in Effective Go, Go Code Review Comments, and production-proven idioms.
All code MUST pass goimports, go vet, and staticcheck (or golangci-lint run) without errors.
Group imports in this order, separated by blank lines:
import (
// 1. Standard library
"context"
"fmt"
"net/http"
// 2. External packages
"github.com/gorilla/mux"
"log/slog"
// 3. Internal/project packages
"github.com/myorg/myproject/internal/service"
)
NEVER use dot imports. Use aliasing only to resolve conflicts.
util, common, helpers, misc, base.Name(), NOT GetName(). Setters: use SetName().NewFoo() returns *Foo. If only one type in package: New().i, n, err, ctx.userCount, retryTimeout._: var _defaultTimeout = 5 * time.Second.error, len, cap, new, make, close).-er suffix (Reader, Writer, Closer).Use var for top-level declarations. Do NOT specify type when it matches the expression:
// ✅ Good
var _defaultPort = 8080
var _logger = slog.Default()
// ❌ Bad — redundant type
var _defaultPort int = 8080
:= for local variables.var only when zero-value initialization is intentional and meaningful.// ✅ Good — zero value is meaningful
var buf bytes.Buffer
// ✅ Good — short declaration
name := getUserName()
ALWAYS use field names. Never rely on positional initialization:
// ✅ Good
user := User{
Name: "Alice",
Email: "alice@example.com",
Age: 30,
}
// ❌ Bad — positional, breaks on field reordering
user := User{"Alice", "alice@example.com", 30}
Omit zero-value fields unless clarity requires them:
// ✅ Good — zero values omitted
user := User{
Name: "Alice",
}
Handle errors and special cases first with early returns. Reduce indentation levels:
// ✅ Good — early return
func process(data []Item) error {
for _, v := range data {
if !v.IsValid() {
log.Printf("invalid item: %v", v)
continue
}
if err := v.Process(); err != nil {
return err
}
v.Send()
}
return nil
}
Eliminate unnecessary else blocks:
// ✅ Good
a := 10
if condition {
a = 20
}
// ❌ Bad
var a int
if condition {
a = 20
} else {
a = 10
}
Group related declarations:
const (
_defaultPort = 8080
_defaultTimeout = 30 * time.Second
)
var (
_validTypes = map[string]bool{"json": true, "xml": true}
_defaultUser = User{Name: "guest"}
)
Function ordering within a file:
New() / constructor functionsReceiver methods should appear immediately after the type declaration.
Soft limit of 99 characters. Break long function signatures:
func (s *Store) CreateUser(
ctx context.Context,
name string,
email string,
opts ...CreateOption,
) (*User, error) {
Use defer for cleanup. It makes intent clear at the point of acquisition:
mu.Lock()
defer mu.Unlock()
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
Start enums at 1 (or use explicit sentinel) so zero-value signals "unset":
type Status int
const (
StatusUnknown Status = iota
StatusActive
StatusInactive
)
time Package Properlytime.Duration for durations, NOT raw integers.time.Time for instants. Use time.Since(start) instead of time.Now().Sub(start).int or float64 and convert internally.// ✅ Good
func poll(interval time.Duration) { ... }
poll(10 * time.Second)
// ❌ Bad
func poll(intervalSecs int) { ... }
poll(10)
Pick one receiver kind per type and use it for every method on that type. A type with both value and pointer methods has a method set that changes depending on how it is held, which is a bug waiting to happen.
Use a pointer receiver when any of these hold — and then use it everywhere:
sync.Mutex or any other field that must not be copiedUse a value receiver for small immutable types: time.Time-like value
objects, enums, and types whose methods only read.
// ✅ Consistent — every method on *Buffer takes a pointer
func (b *Buffer) Write(p []byte) (int, error) { ... }
func (b *Buffer) Len() int { ... }
// ❌ Mixed — Len() on a value copies the mutex
func (b *Buffer) Write(p []byte) (int, error) { ... }
func (b Buffer) Len() int { ... }
Name the receiver one or two letters after the type (s *Server, b *Buffer).
Never this or self. Keep the same name across every method on the type.
Tags are strings the compiler does not check. A typo is silent.
// ✅ Good — backticks, no spaces after commas, explicit names
type User struct {
ID string `json:"id" db:"id"`
Email string `json:"email" db:"email" validate:"required,email"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
password string `json:"-"` // never serialised
}
// ❌ Bad
type User struct {
ID string `json: "id"` // space after the colon: tag is ignored
Email string `json:"email,"` // trailing comma
Token string // no tag: marshals as "Token"
}
Rules:
json:"-" for anything that must never leave the process. Do not rely
on a field being unexported — an unexported field is skipped by
encoding/json silently, which reads as an oversight rather than intent.omitempty omits zero values, so it cannot distinguish "absent" from
"explicitly zero". Use a pointer or a wrapper type when that distinction
carries meaning.go vet includes a structtag check. Run it — it catches the malformed
cases above.Before considering code complete:
goimports runs cleango vet ./... passesgolangci-lint run passes (if configured)