go-guidelines
Go Guidelines
Overview
Idiomatic Go guidelines based on Effective Go and Google Go Style Guide. Go has its own conventions — writing good Go means embracing them, not porting patterns from other languages.
Core Proverbs
- Clear is better than clever — optimize for readability, not cleverness
- A little copying is better than a little dependency — duplicate small functions rather than import heavy packages
- The zero value should be useful — design types so
var x Tworks without initialization - Don't communicate by sharing memory; share memory by communicating — use channels
- Errors are values — program with them, don't just check them
- Don't just check errors, handle them gracefully — add context, decide action
- Reflection is never clear — avoid
reflectunless building frameworks
Formatting
| Rule | Detail |
|---|---|
Use gofmt |
No exceptions — all Go code is gofmt'd |
| Indentation | Tabs, not spaces |
| Line length | No hard limit; break long lines naturally |
| Braces | Opening brace on same line (mandatory — semicolon insertion) |
Import Grouping
import (
// 1. Standard library
"context"
"fmt"
"net/http"
// 2. Third-party packages
"github.com/gorilla/mux"
"go.uber.org/zap"
// 3. Internal packages
"myproject/internal/auth"
"myproject/user"
)
Naming Conventions
| What | Convention | Example |
|---|---|---|
| Packages | Lowercase, single-word, no underscores | bufio, strconv |
| Exported names | MixedCaps (upper first letter) |
ReadWriter |
| Unexported | mixedCaps (lower first letter) |
readBuf |
| Getters | Owner() not GetOwner() |
func (o *Obj) Name() string |
| Setters | SetOwner() |
func (o *Obj) SetName(n string) |
| One-method interfaces | Method name + -er suffix |
Reader, Writer, Stringer |
| Acronyms | All caps throughout | URL, HTTP, ID (not Url, Http, Id) |
| Constants | MixedCaps (not ALL_CAPS) |
MaxRetries, not MAX_RETRIES |
| Receivers | 1-2 letters, consistent | func (s *Server), not func (server *Server) |
| Local variables | Length proportional to scope size | 1-7 lines: i, buf; larger scopes: descriptive |
Doc Comments
// Package sort provides primitives for sorting slices.
package sort
// Fprint formats using the default formats and writes to w.
// It returns the number of bytes written and any write error.
func Fprint(w io.Writer, a ...any) (n int, err error) {
| Doc comment rule | Detail |
|---|---|
| Start with the name of the thing | // Fprint formats... not // This function formats... |
| Full sentences | Capitalized, ending with period |
| Package comment in any file | One // Package name ... per package |
Naming Pitfalls
// BAD: Java/C# naming
func GetUserName() string { ... }
type IReader interface { ... }
// GOOD: Go naming
func UserName() string { ... }
type Reader interface { ... }
// BAD: stuttering in constructors
widget.NewWidget()
user.NewUser()
// GOOD: package name provides context
widget.New()
user.New()
// BAD: ALL_CAPS constants
const MAX_RETRIES = 3
// GOOD: MixedCaps — name by role, not value
const MaxRetries = 3
Control Structures
| Rule | Detail |
|---|---|
Use if init statements |
if err := f(); err != nil { } — keeps scope tight |
| Early return | Avoid else after return |
for has three forms |
C-style, while, infinite |
range is idiomatic |
Prefer for k, v := range over index-based loops |
| Range on string | Iterates runes (UTF-8), not bytes |
| Switch has no fall-through | Unlike C; use comma-separated cases for multiple matches |
| Switch on true | Replaces if-else chains cleanly |
| Type switch | switch v := value.(type) for interface type dispatch |
Full code examples: resources/types-functions.md
Functions
| Rule | Detail |
|---|---|
Return (value, error) |
The Go pattern for fallible operations |
| Named return values | Document meaning in godoc; naked returns OK in short functions only |
defer runs on any return path |
Including panics; arguments evaluated immediately |
defer is LIFO |
Last deferred runs first |
Use defer for cleanup |
Files, locks, connections |
Full code examples: resources/types-functions.md
Data Types
new vs make
| Function | Returns | Use for | Initializes |
|---|---|---|---|
new(T) |
*T |
Any type | Zeroed memory |
make(T, ...) |
T |
Slices, maps, channels only | Internal data structures |
Slices
| Slice rule | Detail |
|---|---|
Always use return value of append |
Underlying array may move |
Pre-allocate with make([]T, 0, cap) |
When size is known or estimable |
len(s) vs cap(s) |
Length is current size, capacity is maximum before reallocation |
| Nil slice is valid | len(nil) == 0, append(nil, x) works |
| Prefer nil for empty slices | var s []T not s := []T{} — nil encodes to null in JSON, empty literal to [] |
Maps
| Map rule | Detail |
|---|---|
| Zero value for missing key | m["absent"] returns zero, not error |
| Always use comma-ok for presence check | v, ok := m[key] |
| Not safe for concurrent access | Use sync.Map or mutex |
| Iteration order is random | Don't rely on order |
Full code examples: resources/types-functions.md
Design Principles
Zero Values Should Be Useful
Design types so var x T works without initialization. Example: var buf bytes.Buffer is ready to use with no New() call. Nil fields should fall back to sensible defaults.
Don't Copy Types with Locks
Copying a sync.Mutex copies its state — undefined behavior. Always use pointer receivers on types containing locks.
A Little Copying > A Little Dependency
Duplicate small utility functions rather than importing a heavy package for one helper.
Security
| Rule | Detail |
|---|---|
crypto/rand not math/rand |
For keys, tokens, secrets — math/rand is predictable |
Build tags for syscall |
//go:build linux when using platform-specific syscalls |
Build tags for cgo |
//go:build cgo — cgo sacrifices memory safety and cross-compilation |
Avoid unsafe package |
Voids all type safety and compatibility guarantees |
Avoid reflect |
Obscures intent, removes compile-time safety |
Methods
Pointer vs Value Receivers
| Receiver | Can modify? | Called on | Use when |
|---|---|---|---|
func (t T) Method() |
No (copy) | Values and pointers | Read-only, small types |
func (t *T) Method() |
Yes | Pointers only* | Mutation, large structs |
* Go auto-takes address for addressable values.
- Don't pass pointers to small basic types — pass values
- Consistency: if any method needs pointer receiver, all should use pointer
Full code examples: resources/patterns.md
Interfaces
Design Principles
| Interface rule | Detail |
|---|---|
| Keep interfaces small | 1-2 methods ideal |
| Accept interfaces, return concrete types | Maximum flexibility for callers |
| Don't export implementation types when interface suffices | Return interface from constructors |
| Interfaces are satisfied implicitly | No implements keyword |
Name one-method interfaces with -er |
Reader, Writer, Closer, Stringer |
| Define in consuming package | Not in implementing package |
| Don't define before use | Wait until you have realistic usage |
| Don't export unused interfaces | If only used internally, keep unexported |
| Prefer synchronous functions | Let callers add concurrency — easier to test and reason about |
Type Assertions
// Safe form — comma-ok
str, ok := value.(string)
if !ok {
// value is not a string
}
// Type switch — multiple type checks
switch v := value.(type) {
case string:
return v
case fmt.Stringer:
return v.String()
default:
return fmt.Sprintf("%v", value)
}
Embedding
// Interface embedding — compose interfaces
type ReadWriter interface {
Reader
Writer
}
// Struct embedding — promote methods
type Job struct {
Command string
*log.Logger // promoted: job.Println() works
}
Initialization
| Init rule | Detail |
|---|---|
init() called after all variable declarations |
Automatic |
| All imports initialized first | Dependency order |
Multiple init() per file allowed |
Run in order |
| Use for verification | Not complex logic |
Full code examples: resources/patterns.md
Functional Options Pattern
Use for 3+ optional configuration fields, library APIs, or when defaults should work out of the box. Define type Option func(*T), create WithX constructors, and apply in NewT(opts ...Option).
| When to use | When NOT to use |
|---|---|
| 3+ optional configuration fields | 1-2 required parameters — just use arguments |
| Library APIs (callers shouldn't know internals) | Internal structs with few fields — use literal |
| Defaults should work out of the box | Config loaded from file — use a config struct |
Full code examples: resources/patterns.md
Constructor & Validation
| Pattern | When |
|---|---|
NewX() *X |
Simple construction, always succeeds |
NewX() (*X, error) |
Validation needed, may fail |
MustX() *X |
Panics on error — only for compile-time constants or tests |
Full code examples: resources/patterns.md
Generics (Go 1.18+)
| Use generics for | Don't use generics for |
|---|---|
| Type-safe collections/containers | Simple functions that work with interface{} |
| Algorithm reuse across types | When only one type is ever used |
| Reducing code duplication | When it makes code harder to read |
| Options/builder patterns | Premature abstraction |
Full code examples: resources/patterns.md
Package Organization
// Domain-based (preferred for applications)
myapp/
├── user/ # User domain
│ ├── user.go
│ ├── store.go
│ └── handler.go
├── order/ # Order domain
│ ├── order.go
│ └── service.go
├── internal/ # Private packages
│ └── db/
└── cmd/
└── server/main.go
// Flat (for libraries and small services)
mylib/
├── mylib.go # Primary types and functions
├── option.go # Options pattern
├── mylib_test.go
└── internal/ # Implementation details
| Rule | Detail |
|---|---|
cmd/ |
Entry points — main packages |
internal/ |
Private — compiler-enforced, cannot be imported outside module |
pkg/ |
Optional — public library code (some projects skip this) |
Avoid models/, utils/, helpers/ |
Too generic — organize by domain |
| One package = one purpose | If you can't name it in one word, split it |
Testing
| Testing rule | Detail |
|---|---|
_test.go suffix |
Test files, excluded from production builds |
Test prefix |
Functions must start with TestXxx(t *testing.T) |
t.Run for subtests |
Enables selective running: go test -run TestAdd/positive |
t.Helper() |
Marks helper functions for better error locations |
t.Cleanup() |
Deferred cleanup that runs after test completes |
t.Parallel() |
Opt-in parallel execution within a test |
testdata/ directory |
Test fixtures, ignored by Go tooling |
| Got before want | t.Errorf("Func(%v) = %v, want %v", input, got, want) |
t.Error over t.Fatal |
Keep going — report all failures in one run |
t.Fatal only for setup |
When test literally cannot proceed |
No t.Fatal from goroutines |
Only call from test function's goroutine |
Full code examples: resources/testing-verification.md
Static Verification
| Tool | Purpose |
|---|---|
go vet ./... |
Built-in static analysis |
golangci-lint run ./... |
Comprehensive linter aggregator |
go test -race ./... |
Detect data races at runtime |
go test -count=1 ./... |
Disable test caching |
go test -cover ./... |
Coverage report |
Full config and examples: resources/testing-verification.md
Anti-Pattern Quick Reference
| Anti-Pattern | Better Alternative |
|---|---|
GetName() getter |
Name() |
Stuttering: user.UserName |
user.Name |
interface{} everywhere |
Specific types or generics |
| Large interfaces (10+ methods) | Small, composed interfaces |
Returning error and ignoring it |
Always handle or explicitly _ = |
else after return |
Early return pattern |
| Naked goroutines (fire-and-forget) | Track with sync.WaitGroup or errgroup |
init() with complex logic |
Explicit initialization in main() |
| Mutable package-level vars | Pass config explicitly |
| Value receiver on large struct | Pointer receiver |
Checking == nil on interface |
Check concrete value (interfaces have type+value) |
panic for expected errors |
Return error |
| Underscore imports without comment | Document why: import _ "pkg" // register driver |
new(T) when make is needed |
make for slices/maps/channels |
Ignoring append return value |
Always s = append(s, ...) |
| Config struct with 10+ fields | Functional options pattern |
| No validation in constructor | NewX() (*X, error) with validate() |
utils/ or helpers/ package |
Organize by domain, not by kind |
models/ package for all types |
Put types where they're used |
Not using t.Helper() |
Test helpers report wrong line numbers |
Not using t.Cleanup() |
Cleanup may not run on t.Fatal() |
go test without -race |
Data races go undetected |
Copying struct with sync.Mutex |
Use pointer receiver, never copy |
math/rand for secrets |
crypto/rand for tokens, keys, secrets |
In-band errors (-1, empty string) |
Return (value, error) or (value, bool) |
| Defining interface in implementing package | Define where consumed |
ALL_CAPS constants |
MixedCaps — Go convention |
import . "pkg" (dot import) |
Makes code origin unclear |
| Context in struct field | Always pass context.Context as first parameter |
| Clever one-liners | Clear, readable multi-line code |
reflect for simple tasks |
Generics or type assertions |
Build & Deploy
Always Use Makefile
Before running go build or any build command, check if a Makefile exists. If it does, use it — the Makefile encodes project-specific build steps (ldflags, embedding, code generation) that raw commands miss.
| Situation | Action |
|---|---|
| Makefile exists with relevant target | make deploy, make build, make test, etc. |
| Makefile exists, no matching target | List targets, pick closest match |
| No Makefile | Fall back to go build, go test, etc. |
Permissions & Ownership
Before building or deploying, check file permissions and ownership. Fix to match the project majority:
# Identify majority owner:group
stat -c '%U:%G' * | sort | uniq -c | sort -rn | head -5
# Fix if needed
chown -R <user>:<group> .
Temporary Files
| File type | Location |
|---|---|
| Build intermediates, scratch files | /tmp — never the project directory |
| Final build artifacts | Project output dir or $GOBIN |
| Downloaded dependencies | Standard location (~/go/pkg/) |
Exception: If project instructions (CLAUDE.md, Makefile) specify a different temp location, follow those.
Test File Placement
| Test type | Location |
|---|---|
| Temporary (debugging, one-off) | /tmp |
| Permanent, Go convention | *_test.go beside the source file |
| Permanent, integration/e2e | tests/ directory (create if it doesn't exist) |
| Specific instructions exist | Follow those |
Resources
Detailed code examples are organized into resource files for progressive disclosure:
- Types, Functions & Control Flow — Control structure patterns (
if/for/switch), function signatures, defer,newvsmake, composite literals, slices, maps, and printing - Patterns — Methods (pointer vs value receivers), initialization (
iota,init), functional options, constructor validation, and generics - Testing & Verification — Table-driven tests, parallel tests, test helpers, testify, golangci-lint config,
go vet, race detector, and coverage
More from peixotorms/odinlayer-skills
elementor-development
Use when building Elementor addons, creating custom widgets, or managing Elementor components. Covers Widget_Base class (get_name, get_title, get_icon, register_controls, render, content_template), widget registration via elementor/widgets/register hook, addon structure and plugin header, wp_enqueue_script for widget assets, get_script_depends, get_style_depends, inline editing toolbars, custom widget categories, manager registration (register/unregister), selector tokens ({{WRAPPER}}), deprecation handling, and Elementor CLI commands.
65elementor-hooks
Use when hooking into Elementor lifecycle events, injecting controls, filtering widget output, or using the JS APIs. Covers elementor/init, elementor/element/before_section_end, elementor/element/after_section_end, elementor/widget/render_content filter, elementor/frontend/after_enqueue_styles, frontend JS hooks (elementorFrontend.hooks, frontend/element_ready), editor JS hooks (elementor.hooks), $e.commands API ($e.run, $e.commands.register), $e.routes, $e.hooks (registerUIBefore, registerUIAfter), control injection patterns, CSS file hooks, forms hooks (Pro), and query filters.
26elementor-themes
Use when building Elementor-compatible themes, registering theme locations, creating dynamic tags, or extending the Finder. Covers register_location, theme_builder locations, elementor_theme_do_location, Theme_Document and theme conditions, Tag_Base for dynamic tags (register_tag, get_value, render), Finder extension (Category_Base, register via elementor/finder/register), Context_Menu customization (elements/context-menu/groups filter), Hello Elementor theme (elementor-hello-theme, hello_elementor_* filters), and hosting page cache integration hooks.
25elementor-controls
Use when adding controls to Elementor widgets, creating custom controls, or referencing control type parameters. Covers add_control with types (TEXT, SELECT, SLIDER, COLOR, MEDIA, REPEATER, CHOOSE, NUMBER, SWITCHER, URL, ICONS), TYPOGRAPHY and BACKGROUND group controls, BORDER, BOX_SHADOW group controls, add_responsive_control, add_group_control, CSS selectors ({{WRAPPER}}, {{VALUE}}), condition and conditions for conditional display, dynamic content tags, POPOVER_TOGGLE, and global styles integration.
16elementor-forms
Use when creating custom Elementor form actions, custom form field types, form validation, or processing form submissions. Covers Elementor Pro forms (ElementorPro\Modules\Forms), Action_Base (get_name, get_label, run, register_settings_section, on_export), after_submit processing, Field_Base (field_type, render field HTML, validation callback, update_controls), content_template for editor preview, form action registration, export_type handling, update_record patterns, elementor_pro/forms/validation hook, email filters, and webhook response handling.
12flyonui
Use when building with FlyonUI — Tailwind CSS component library with CSS classes and optional JS plugins. Covers CSS component classes, JS plugin system (accordion, carousel, collapse, combobox, datatable, dropdown, select, tabs, tooltip, etc.), theming, installation, class reference, and plugin initialization via MCP tools.
9