Claude-skill-registry functions-methods
Reviews Go function signatures for parameter ordering, return value conventions, and receiver patterns. Use when reviewing function signatures, designing APIs, or seeing complex parameter lists.
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/functions-methods" ~/.claude/skills/majiayu000-claude-skill-registry-functions-methods && rm -rf "$T"
manifest:
skills/data/functions-methods/SKILL.mdsource content
Functions and Methods
Purpose
Establish consistent patterns for function and method signatures in RMS Go code. Well-designed function signatures make APIs intuitive and maintainable.
Core Principles
- Context first -
is always the first parameterctx context.Context - Error last -
is always the last return valueerror - Limit parameters - Use parameter structs for 4+ arguments
- Consistent receivers - Same name across all methods of a type
Parameter Ordering
Standard Order
func Name(ctx context.Context, id rms.ID, options ...Option) (*Result, error) ^ ^ ^ ^ ^ | | | | | 1. Context 2. IDs 3. Variadic opts 4. Data 5. Error
Context Always First
// DO: Context first func (s *Service) GetTask(ctx context.Context, id rms.ID) (*Task, error) func (s *Service) CreateTask(ctx context.Context, params CreateTaskParams) (*Task, error) func (s *Service) List(ctx context.Context, filter TaskFilter) ([]*Task, error) // DON'T: Context not first func (s *Service) GetTask(id rms.ID, ctx context.Context) (*Task, error) func CreateTask(params CreateTaskParams, ctx context.Context) (*Task, error)
IDs Before Data Structures
// DO: IDs before larger structures func (s *Service) Update(ctx context.Context, id rms.ID, updates TaskUpdates) error func (s *Service) AssignTask(ctx context.Context, taskID, assigneeID rms.ID) error // DON'T: Data structures before IDs func (s *Service) Update(ctx context.Context, updates TaskUpdates, id rms.ID) error
Error Always Last
// DO: Error last in return values func (s *Service) Get(ctx context.Context, id rms.ID) (*Task, error) func (s *Service) List(ctx context.Context) ([]*Task, int64, error) // DON'T: Error not last func (s *Service) Get(ctx context.Context, id rms.ID) (error, *Task)
Return Values
Named Return Values
Use named returns sparingly - primarily for documentation or defer usage.
// DO: Named returns for complex functions with multiple returns func (p *Parser) Parse(input string) (result *AST, remaining string, err error) { // Named returns document what each value represents } // DO: Named returns when defer needs to modify them func (s *Service) ProcessWithCleanup(ctx context.Context) (err error) { lock := s.acquireLock() defer func() { if unlockErr := lock.Unlock(); unlockErr != nil { if err == nil { err = unlockErr } } }() // ... } // DON'T: Named returns for simple functions func (t *Task) ID() (id rms.ID) { // Unnecessary return t.id } // DO: Simple functions without named returns func (t *Task) ID() rms.ID { return t.id }
Naked Returns
Avoid naked returns - they reduce clarity.
// DON'T: Naked returns func calculate(a, b int) (result int, err error) { result = a + b return // What are we returning? } // DO: Explicit returns func calculate(a, b int) (result int, err error) { result = a + b return result, nil } // Or without named returns func calculate(a, b int) (int, error) { result := a + b return result, nil }
Returning Zero Values
Return explicit zero values when appropriate.
// DO: Return nil explicitly for pointers func (s *Store) Get(ctx context.Context, id rms.ID) (*Task, error) { if !exists { return nil, ErrNotFound } return task, nil } // DO: Return empty slice, not nil, when list has no results func (s *Store) List(ctx context.Context) ([]*Task, error) { tasks := make([]*Task, 0) // Empty slice, not nil // ... return tasks, nil }
Parameter Structs
When to Use
Use parameter structs when:
- Function has 4+ parameters
- Parameters are logically grouped
- Function signature changes frequently
- Optional parameters exist
// DON'T: Too many parameters func CreateTask( ctx context.Context, title string, description string, workflowID rms.ID, actorID rms.ID, priority Priority, dueDate time.Time, metadata map[string]any, ) (*Task, error) // DO: Parameter struct type CreateTaskParams struct { Title string Description string WorkflowID rms.ID ActorID rms.ID Priority Priority DueDate time.Time Metadata map[string]any } func CreateTask(ctx context.Context, params CreateTaskParams) (*Task, error)
Parameter Struct Design
// DO: Validate in struct method type CreateTaskParams struct { Title string `json:"title"` Description string `json:"description,omitempty"` WorkflowID rms.ID `json:"workflowId"` ActorID rms.ID `json:"actorId"` Priority Priority `json:"priority,omitempty"` DueDate *time.Time `json:"dueDate,omitempty"` Metadata map[string]any `json:"metadata,omitempty"` } func (p CreateTaskParams) Validate() error { if p.Title == "" { return errors.New("title is required") } if p.WorkflowID == "" { return errors.New("workflow ID is required") } if p.ActorID == "" { return errors.New("actor ID is required") } return nil } // Usage func (s *Service) CreateTask(ctx context.Context, params CreateTaskParams) (*Task, error) { if err := params.Validate(); err != nil { return nil, fmt.Errorf("validate params: %w", err) } // ... }
Method Receivers
Pointer vs Value Receivers
Use pointer receivers when:
- Method modifies the receiver
- Receiver is large (struct with many fields)
- Consistency - if any method needs pointer, use pointer for all
// DO: Pointer receiver for mutation func (t *Task) SetStatus(s Status) { t.status = s t.updatedAt = time.Now() } // DO: Pointer receiver for large structs func (t *Task) Clone() *Task { return &Task{ ID: t.ID, Title: t.Title, Description: t.Description, // ... } } // DO: Value receiver for immutable access on small types func (s Status) String() string { return string(s) } func (p Priority) IsHigh() bool { return p >= PriorityHigh }
Receiver Naming
Use short (1-2 letter) names, consistent across all methods.
// DO: Short, consistent receiver names func (t *Task) ID() rms.ID { return t.id } func (t *Task) Status() Status { return t.status } func (t *Task) SetStatus(s Status) { t.status = s } func (t *Task) Validate() error { /* ... */ } func (t *Task) AddAction(a Action) { /* ... */ } // DON'T: Long or inconsistent names func (task *Task) ID() rms.ID func (this *Task) Status() Status func (t *Task) SetStatus(s Status) func (tsk *Task) Validate() error
Avoid Self/This
// DON'T: self/this are not idiomatic Go func (self *Task) Validate() error func (this *Task) Clone() *Task // DO: Short type-derived name func (t *Task) Validate() error func (t *Task) Clone() *Task
Variadic Functions
Use for Optional Parameters
// DO: Variadic for options func NewTaskFactory(opts ...TaskFactoryOption) *TaskFactory { f := &TaskFactory{ priority: PriorityMedium, status: StatusPending, } for _, opt := range opts { opt(f) } return f } // Usage factory := NewTaskFactory( WithDefaultPriority(PriorityHigh), WithValidator(validator), )
Variadic for Homogeneous Lists
// DO: Variadic for multiple items of same type func (t *Task) AddTags(tags ...string) { t.tags = append(t.tags, tags...) } func JoinErrors(errs ...error) error { return errors.Join(errs...) } // Usage task.AddTags("urgent", "review-needed", "q4")
Constructor Functions
NewX Pattern
// DO: NewX constructor func NewTaskService(store TaskStore, opts ...ServiceOption) *TaskService { s := &TaskService{ store: store, logger: defaultLogger, timeout: defaultTimeout, } for _, opt := range opts { opt(s) } return s } // DO: NewX with required dependencies func NewTaskFactory(validator Validator) *TaskFactory { return &TaskFactory{ validator: validator, priority: PriorityMedium, } }
Must Pattern for Initialization
// DO: Must prefix for panic-on-error constructors func MustNewClient(addr string) *Client { client, err := NewClient(addr) if err != nil { panic(fmt.Sprintf("create client: %v", err)) } return client } // Usage in init or main var client = MustNewClient(os.Getenv("SERVER_ADDR")) // DON'T: Use Must in regular business logic func process() { client := MustNewClient(addr) // Dangerous! Will panic on error }
Function Length
Keep Functions Focused
Functions should do one thing well.
// DON'T: Function doing too much func (s *Service) ProcessTask(ctx context.Context, id rms.ID) error { // Fetch task (10 lines) // Validate task (15 lines) // Transform data (20 lines) // Save to database (10 lines) // Publish event (10 lines) // Send notification (15 lines) return nil } // DO: Extract into focused functions func (s *Service) ProcessTask(ctx context.Context, id rms.ID) error { task, err := s.fetchTask(ctx, id) if err != nil { return fmt.Errorf("fetch task: %w", err) } if err := s.validateTask(task); err != nil { return fmt.Errorf("validate: %w", err) } transformed := s.transformTask(task) if err := s.saveTask(ctx, transformed); err != nil { return fmt.Errorf("save: %w", err) } if err := s.publishEvent(ctx, transformed); err != nil { return fmt.Errorf("publish: %w", err) } return nil }
Quick Reference
| Pattern | Convention |
|---|---|
| Context parameter | Always first |
| Error return | Always last |
| ID parameters | Before data structures |
| 4+ parameters | Use parameter struct |
| Receiver name | 1-2 letters, consistent |
| Pointer receiver | For mutation or large structs |
| Value receiver | For small, immutable types |
| Constructor | pattern |
| Panic constructor | pattern |
Function Checklist
- Context is first parameter?
- Error is last return value?
- Parameters under 4, or using struct?
- Receiver name is short and consistent?
- Named returns only where needed?
- No naked returns?
See Also
- EXAMPLES.md - Extended function examples
- naming-convention - Function naming
- functional-options - Option patterns
- struct-interface - Struct design