Part 4 · 1 chapters · ~8 min

Idioms

Effective Go in practice: errors handled where they occur, small interfaces, accept interfaces and return structs, the functional options pattern, constructors, table-driven tests, defer for cleanup, zero values that are useful, package layout, and what not to import from other languages.

9

Writing Go like Go

code
// functional options: readable constructors with defaults
type Server struct{ addr string; readTimeout time.Duration; logger *slog.Logger }
type Option func(*Server)
func WithReadTimeout(d time.Duration) Option { return func(s *Server) { s.readTimeout = d } }
func NewServer(addr string, opts ...Option) *Server {
	s := &Server{addr: addr, readTimeout: 5 * time.Second, logger: slog.Default()}
	for _, o := range opts { o(s) }
	return s
}
srv := NewServer(":8080", WithReadTimeout(10*time.Second))

// defer for cleanup, in reverse order
f, err := os.Open(path); if err != nil { return err }; defer f.Close()
tx, err := db.BeginTx(ctx, nil); if err != nil { return err }; defer tx.Rollback()   // no-op after Commit
idiominstead of
if err != nil { return fmt.Errorf("context: %w", err) }exceptions and try/catch
small interfaces defined by the consumerlarge interfaces defined up front by the implementer
flat packages by domain (ledger, transfers)layers named models, utils, helpers
composition and embeddinginheritance hierarchies
the standard library first (net/http, slog, database/sql)a framework for everything

A common layout: cmd/ledger/main.go for binaries, internal/ for packages other modules may not import, domain packages inside it. Avoid pkg/ and deep hierarchies until you need them.