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.
Scanned 2/10/2026
Install via CLI
openskills install majiayu000/claude-skill-registry---
description: 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.
---
# 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
1. **Context first** - `ctx context.Context` is always the first parameter
2. **Error last** - `error` is always the last return value
3. **Limit parameters** - Use parameter structs for 4+ arguments
4. **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
```go
// 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
```go
// 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
```go
// 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.
```go
// 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.
```go
// 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.
```go
// 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
```go
// 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
```go
// 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
```go
// 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.
```go
// 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
```go
// 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
```go
// 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
```go
// 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
```go
// 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
```go
// 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.
```go
// 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 | `NewX` pattern |
| Panic constructor | `MustNewX` 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](./EXAMPLES.md) - Extended function examples
- [naming-convention](../naming-convention/Skill.md) - Function naming
- [functional-options](../functional-options/Skill.md) - Option patterns
- [struct-interface](../struct-interface/Skill.md) - Struct design
No comments yet. Be the first to comment!