Skip to content
Back to skills

Best Practices

ASecurity

Enforces Go idioms and best practices including error handling, interface

  • 4 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added June 12, 2026
code-qualitygosqltestingrefactoringgitdatabaseperformancedocumentation

Works with

  • cli

Security analysis

A100/100

Scanned June 12, 2026

npx -y skills add paulpas/agent-skill-router --skill best-practices --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Best Practices?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Best Practices
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/paulpas-best-practices/badge)](https://www.skillsdirectory.com/skills/paulpas-best-practices)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---




name: best-practices
description: Enforces Go idioms and best practices including error handling, interface
  design, testing conventions, and code organization for maintainable applications.
license: MIT
compatibility: opencode
metadata:
  version: "1.0.0"
  domain: go
  role: implementation
  scope: implementation
  output-format: code
  content-types:
  - code
  - guidance
  - do-dont
  - examples
  triggers: go best practices, go idioms, golang conventions, go error handling, interface
    design, go naming, idiomatic go
  archetypes:
  - tactical
  anti_triggers:
  - brainstorming
  - vague ideation
  response_profile:
    verbosity: low
    directive_strength: high
    abstraction_level: operational
  related-skills: modular-design, cloud-development, testing-strategies
  maturity: stable
  completeness: 95
  exampleCount: 3




---




# Go Best Practices

Senior engineer enforcing idiomatic Go conventions — writing clean, maintainable Go code that reads like well-structured English. This skill covers error handling, interface design, naming, package organization, and the conventions that make Go code predictable and reviewable.

## TL;DR Checklist

- [ ] Return errors explicitly; never ignore returned errors without intentional handling
- [ ] Design interfaces small and consumer-specific (interface segregation)
- [ ] Name packages after their purpose, not their type (e.g. `cache`, not `cachetypes`)
- [ ] Use `context.Context` for all operations that span request boundaries
- [ ] Write table-driven tests for every exported function
- [ ] Document exported symbols with `// Package doc` and `// Function doc` comments

---

## When to Use

Use this skill when:

- Writing new Go code and wanting to ensure idiomatic conventions
- Reviewing Go code for style, correctness, and adherence to community standards
- Onboarding a developer to a Go codebase with established conventions
- Refactoring legacy Go code to follow modern idioms (Go 1.21+)
- Establishing coding standards for a new Go project

---

## When NOT to Use

Avoid this skill for:

- Low-level systems code where performance overrides readability (use `unsafe` patterns instead)
- One-off scripts or throwaway programs where conventions add unnecessary overhead
- Learning Go from scratch without context (pair with a fundamentals reference first)

---

## Core Workflow

1. **Evaluate the Code Structure** — Check package organization, naming, and exported symbol visibility.
   **Checkpoint:** Every exported function and type has a godoc comment explaining its purpose.

2. **Enforce Error Handling** — Verify all errors are explicitly handled at call sites. No silent swallowing.
   **Checkpoint:** Every `err != nil` check has a meaningful action (return, log with context, retry).

3. **Review Interface Design** — Ensure interfaces are small, consumer-specific, and defined by consumers.
   **Checkpoint:** Interfaces should fit on one screen. If an interface has more than 3-4 methods, consider splitting.

4. **Check Naming Conventions** — Verify names follow Go's naming rules: short, precise, camelCase for exports.
   **Checkpoint:** Package names are lowercase, single words, no underscores.

5. **Validate Testing Coverage** — Ensure table-driven tests cover happy path, error paths, and edge cases.
   **Checkpoint:** Every exported function has at least one test case for normal input and one for error input.

---

## Implementation Patterns

### Pattern 1: Error Handling (❌ BAD vs ✅ GOOD)

Go's error handling must be explicit. Ignoring errors is a bug, not an optimization.

#### ❌ BAD — Silent Error Swallowing

```go
// ❌ BAD: ignoring an error silently — the caller has no way to know something failed
func ProcessFile(path string) error {
	data, err := os.ReadFile(path)
	// err is ignored — file might not exist, permissions denied, etc.
	_ = err

	content := strings.ToUpper(string(data))
	return nil
}
```

**What's wrong:**
- Error is explicitly discarded with `_ = err`, hiding failure from the caller
- The function claims success (`return nil`) even when the file read failed
- No way for the caller to distinguish between "processed 0 bytes" and "file not found"

#### ✅ GOOD — Explicit Error Propagation

```go
// ProcessFile reads a file, uppercases its content, and returns the result.
// Returns os.PathError if the file cannot be read.
func ProcessFile(path string) ([]byte, error) {
	if path == "" {
		return nil, fmt.Errorf("process file: path must not be empty")
	}

	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("process file: read %s: %w", path, err)
	}

	return bytes.ToUpper(data), nil
}
```

**Why this works:**
- Guard clause rejects invalid input early (Early Exit law)
- Error is wrapped with context using `%w` for error chain inspection
- Return type `([]byte, error)` makes success/failure explicit to the caller

---

### Pattern 2: Interface Design (❌ BAD vs ✅ GOOD)

Go interfaces should be small, consumer-defined, and focused.

#### ❌ BAD — Overly Broad Interface

```go
// ❌ BAD: interface defined by the implementer, not the consumer
// Forces consumers to depend on methods they don't use
type Service interface {
	// A service that does everything
	CreateUser(ctx context.Context, req CreateUserRequest) (*User, error)
	UpdateUser(ctx context.Context, id string, req UpdateUserRequest) error
	DeleteUser(ctx context.Context, id string) error
	GetUser(ctx context.Context, id string) (*User, error)
	ListUsers(ctx context.Context, filter ListFilter) ([]*User, error)
	SendEmail(ctx context.Context, to string, subject string, body string) error
	GenerateReport(ctx context.Context, format string) ([]byte, error)
}
```

**What's wrong:**
- Single huge interface violates interface segregation principle
- Consumers of just `GetUser` still depend on `SendEmail` and `GenerateReport`
- Impossible to mock without implementing all 7 methods
- Interface defined by the implementer, not by its consumers

#### ✅ GOOD — Small, Consumer-Specific Interfaces

```go
// UserRepository defines operations for user persistence.
// Implemented by concrete types; consumed by handlers and services.
type UserRepository interface {
	GetByID(ctx context.Context, id string) (*User, error)
	Create(ctx context.Context, user *User) error
	Update(ctx context.Context, user *User) error
	Delete(ctx context.Context, id string) error
}

// EmailService defines operations for sending email.
type EmailService interface {
	Send(ctx context.Context, msg EmailMessage) error
}

// ListableUsers is a constraint for types that support user listing.
type ListableUsers interface {
	ListUsers(ctx context.Context, filter ListFilter) ([]*User, error)
}
```

**Why this works:**
- Each interface is small and focused on a single responsibility
- Consumers only depend on what they actually need
- Easy to mock for testing — a mock needs only 3-4 methods max
- Interfaces are defined by consumers, not by the implementation

---

### Pattern 3: Context Usage (❌ BAD vs ✅ GOOD)

Context propagation is essential for cancellable, deadline-aware operations.

#### ❌ BAD — Ignoring Context

```go
// ❌ BAD: database query ignores context — cannot be cancelled or timed out
func GetUserByID(id string) (*User, error) {
	row := db.QueryRow("SELECT id, name, email FROM users WHERE id = $1", id)
	// no context — query runs forever if the DB is slow
	var user User
	err := row.Scan(&user.ID, &user.Name, &user.Email)
	if err != nil {
		return nil, fmt.Errorf("get user: %w", err)
	}
	return &user, nil
}
```

**What's wrong:**
- No context means the query cannot be cancelled on client disconnect
- No timeout — a slow query blocks the goroutine indefinitely
- Violates the principle that all operations spanning request boundaries accept context

#### ✅ GOOD — Context-Aware Operations

```go
// GetUserByID retrieves a user by their unique ID.
// The operation is cancellable via ctx and respects its deadline.
func GetUserByID(ctx context.Context, id string) (*User, error) {
	if id == "" {
		return nil, fmt.Errorf("get user by id: id must not be empty")
	}

	row := db.QueryRowContext(ctx,
		"SELECT id, name, email FROM users WHERE id = $1", id,
	)

	var user User
	if err := row.Scan(&user.ID, &user.Name, &user.Email); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return nil, fmt.Errorf("get user by id: user %s not found", id)
		}
		return nil, fmt.Errorf("get user by id: %w", err)
	}

	return &user, nil
}
```

**Why this works:**
- Context enables cancellation and timeout propagation
- `QueryRowContext` respects the deadline — no hung queries
- Error wrapping preserves the error chain for inspection
- Guard clause validates input before hitting the database

---

## Constraints

### MUST DO

- **MUST** return errors explicitly and wrap them with context using `%w`
- **MUST** define interfaces small (under 5 methods) and consumer-specific
- **MUST** document every exported function, type, and package with godoc comments
- **MUST** use `context.Context` for all operations that span request or request-like boundaries
- **MUST** name packages after their purpose in lowercase single words
- **MUST** write table-driven tests for all exported functions
- **MUST** use `errors.Is` and `errors.As` for error inspection instead of string comparison

### MUST NOT DO

- **MUST NOT** ignore returned errors — not even with `_ = err` or blank identifier
- **MUST NOT** define interfaces in the same package that implements them
- **MUST NOT** use `panic()` for error handling — only for truly unrecoverable conditions at startup
- **MUST NOT** use `interface{}` — use `any` (Go 1.18+) for untyped values
- **MUST NOT** embed large interfaces to create sub-interfaces — composition wins over inheritance
- **MUST NOT** name packages with underscores, hyphens, or plural forms

---

## Related Skills

| Skill | Purpose |
|-------|---------|
| `modular-design` | Designs package boundaries and dependency injection architecture |
| `cloud-development` | Cloud-native patterns (graceful shutdown, health checks, config) |
| `testing-strategies` | Table-driven tests, mocks, benchmarks, and integration testing |
| `error-handling` | Custom error types, retry patterns, and failure recovery strategies |

---

## Live References

> Authoritative documentation links for this skill's domain. The model follows markdown links at load time to resolve external references and inline content.

- [Effective Go Guide](https://go.dev/doc/effective_go)
- [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
- [Go Proverbs](https://go-proverbs.github.io/)
- [Go Blog - Style Guidelines](https://go.dev/blog/gofmt)
- [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…