From effective-go
Idiomatic Go architecture and design. Use whenever writing Go code, designing Go packages, or helping with Go projects. Focuses on what makes Go code structurally different from other languages -- zero-value types, consumer-defined interfaces, composition via embedding. Triggers on any Go code generation, package design, or architecture discussion.
npx claudepluginhub human-frontier-labs-inc/human-frontier-labs-marketplace --plugin effective-goThis skill uses the workspace's default tool permissions.
You already write syntactically correct Go. You use named fields, defer after acquisition, guard clauses, `fmt.Errorf` wrapping. That's not the problem.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
You already write syntactically correct Go. You use named fields, defer after acquisition, guard clauses, fmt.Errorf wrapping. That's not the problem.
The problem is architectural taste -- the design decisions that happen before and during coding that make Go programs feel like Go, not translated Java. This skill focuses exclusively on those decisions.
Every struct you define: ask "does var x MyType work?" If calling a method on a zero-value instance panics or does nothing useful, redesign.
The pattern: lazy-initialize maps and slices on first write. Use nil pointers to mean "use default." Design so new(T) returns something usable.
type Store struct {
mu sync.RWMutex
items map[string]*Item // nil is fine -- lazy-init on write
}
func (s *Store) Put(key string, item *Item) {
s.mu.Lock()
defer s.mu.Unlock()
if s.items == nil {
s.items = make(map[string]*Item)
}
s.items[key] = item
}
func (s *Store) Get(key string) (*Item, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
item, ok := s.items[key] // nil map read returns zero value -- safe
return item, ok
}
Standard library examples: bytes.Buffer, sync.Mutex, http.Client -- all usable at zero value. Design your types the same way.
When a constructor IS required (network connections, validated config), that's fine -- but it should be the exception. Name it New (one type per package) or NewThing (multiple types).
In Go, interfaces are defined where they're consumed, not where they're implemented. This is the single most important Go design principle and the one most commonly violated.
The process:
implements keyword// Your handler package needs to fetch users. It calls ONE method.
// Define the interface HERE, not in the database package.
type UserFetcher interface {
Fetch(ctx context.Context, id string) (*User, error)
}
func HandleGetUser(store UserFetcher, id string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, err := store.Fetch(r.Context(), id)
// ...
}
}
Why this matters: any concrete type with a Fetch method works. Your database implementation, a mock for tests, a cache wrapper -- they all satisfy UserFetcher without knowing it exists.
Red flags you're doing it wrong:
IFoo or FooInterfaceAccept interfaces, return structs. Your function parameters should be narrow interfaces. Your return types should be concrete structs so callers get the full API.
Embed a type to promote ALL its methods to the outer type. This is how Go does composition -- no inheritance, no subclassing, just automatic delegation.
When to embed:
// Wrap http.ServeMux with extra behavior
type Server struct {
*http.ServeMux // promotes Handle, HandleFunc, ServeHTTP
timeout time.Duration
}
// Add logging to any type
type Service struct {
*log.Logger // promotes Print, Printf, Println, etc.
db *sql.DB
}
svc.Println("starting up...") // calls the embedded Logger
// Compose interfaces
type ReadWriteCloser interface {
io.Reader
io.Writer
io.Closer
}
// Test mocks -- satisfy a big interface, override 1-2 methods
type mockDB struct {
*RealDB // satisfies everything
fetchFn func(string) *User // override just Fetch
}
func (m *mockDB) Fetch(id string) *User { return m.fetchFn(id) }
When NOT to embed: if you only need 1-2 methods from the embedded type, write explicit forwarding instead. Embedding promotes everything, which can expose methods you didn't intend.
Any finite set of known values (event types, states, statuses) gets a named type. Never use raw strings.
type Status string
const (
StatusPending Status = "pending"
StatusRunning Status = "running"
StatusComplete Status = "complete"
StatusFailed Status = "failed"
)
// Now the compiler helps you -- you can't accidentally pass "pneding"
func (j *Job) SetStatus(s Status) { j.status = s }
This applies to event types, states, categories, roles -- anything where the set of valid values is known at design time.
Before writing a loop, check if the standard library already does it. Go developers reach for sort.Slice, slices.SortFunc, strings.Builder, maps.Keys, sync.Pool without thinking. If you find yourself implementing something that sounds like it should exist, it does.
Never hand-roll sorting. Never reimplement string joining. Never build your own sync primitives.
These are non-negotiable tells that code wasn't written by a Go developer:
os.Exit() outside main() -- return errors up the stackGetFoo() getters -- the getter for name is Name(), setter is SetName()*ssh.Session, it's not really an interface. Return io.Reader or io.ReadCloserint, timestamps are time.Time, not stringsFor full pattern reference: references/effective-go-patterns.md