Claude-skill-registry go-idioms
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/go-idioms" ~/.claude/skills/majiayu000-claude-skill-registry-go-idioms && rm -rf "$T"
manifest:
skills/data/go-idioms/SKILL.mdsource content
Go Idioms
Boring, explicit, race-safe Go. Accept interfaces, return structs.
Error Handling
Always wrap with context at package boundaries:
// Use %w to preserve error chain return fmt.Errorf("fetching user %s: %w", userID, err) // Check wrapped errors if errors.Is(err, sql.ErrNoRows) { ... } var paymentErr *PaymentError if errors.As(err, &paymentErr) { ... }
Never:
%v (loses type), raw errors from exported functions, generic context.
Interface Design
Define interfaces in consuming package, not provider:
// notification/sender.go (consumer defines interface) type EmailSender interface { Send(ctx context.Context, to, subject, body string) error } // email/client.go (provider implements) type Client struct { ... } func (c *Client) Send(ctx context.Context, to, subject, body string) error { ... }
Small interfaces (1-3 methods). Compose larger from smaller:
type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type ReadWriter interface { Reader; Writer }
Compile-time verification:
var _ EmailSender = (*Client)(nil)
Concurrency
Always propagate context:
func FetchData(ctx context.Context) ([]byte, error) { select { case <-ctx.Done(): return nil, ctx.Err() case result := <-dataChan: return result, nil } }
Bounded concurrency (semaphore):
sem := make(chan struct{}, 10) for _, item := range items { sem <- struct{}{} go func(item Item) { defer func() { <-sem }() process(item) }(item) }
Race safety: Always run
go test -race ./...
Package Design
internal/ user/ # Domain: single purpose user.go service.go repository.go order/ # Another domain app/ # Dependency wiring cmd/ api/ # Entry points
Rules:
- Single purpose per package
- No generic names (utils, helpers, common)
- No circular dependencies
- Export only what's necessary
Dependency Injection
// Constructor accepts interfaces func NewUserService(repo UserRepository, mailer EmailSender) *UserService { return &UserService{repo: repo, mailer: mailer} } // Wire in app/ package func NewApp() *App { repo := postgres.NewUserRepo(db) mailer := sendgrid.NewClient(apiKey) userSvc := user.NewUserService(repo, mailer) return &App{UserService: userSvc} }
Anti-Patterns
- Goroutines without cancellation path (leaks)
- Monolithic interfaces (10+ methods)
- Framework-like inheritance patterns
- Reflection when explicit types work
- Global singletons for dependencies
- Generic everything (overuse of generics)
/interface{}
without justificationany
Embrace Boring
- Explicit error handling at each step
- Standard library first (
,map
,[]T
)sort.Slice - Table-driven tests
- Struct composition, not inheritance
- Clear, verbose code over clever code