| name | go |
| description | Guides Go development with idiomatic patterns, tooling, and project structure. ALWAYS trigger on "go project", "go module", "go mod", "goroutine", "channel", "go test", "go build", "golangci-lint", "go interface", "go error handling", "go concurrency", "go struct", "go anti-pattern", "go best practices", "go tooling", "go lint". Use when setting up Go projects, writing idiomatic Go, choosing concurrency patterns, or configuring tooling. Different from testing skill which covers general test strategy; this covers Go-specific testing patterns and tooling configs. |
Go Domain Skill
Error Handling
Go uses explicit error returns, not exceptions. Every error is a value you handle at the call site.
func ParseConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading config %s: %w", path, err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parsing config: %w", err)
}
return &cfg, nil
}
var (
ErrNotFound = errors.New("not found")
ErrForbidden = errors.New("forbidden")
)
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation: %s: %s", e.Field, e.Message)
}
if errors.Is(err, ErrNotFound) { }
var ve *ValidationError
if errors.As(err, &ve) { }
See references/error-handling.md for wrapping strategies, panic/recover, error handling in goroutines, domain error design.
Interfaces
Go interfaces are satisfied implicitly -- no implements keyword. Define them where they're consumed, not where they're implemented.
type UserStore interface {
GetUser(ctx context.Context, id string) (*User, error)
}
func NewUserService(store UserStore) *UserService {
return &UserService{store: store}
}
| Guideline | Why |
|---|
| 1-3 method interfaces | Easier to implement, compose, mock |
| Define at consumer | Decouples packages, avoids import cycles |
| Accept interface, return struct | Callers get flexibility, producers stay concrete |
| Embed for composition | io.ReadWriter = io.Reader + io.Writer |
Concurrency
func FetchAll(ctx context.Context, urls []string) ([]Result, error) {
g, ctx := errgroup.WithContext(ctx)
results := make([]Result, len(urls))
for i, url := range urls {
g.Go(func() error {
res, err := fetch(ctx, url)
if err != nil {
return err
}
results[i] = res
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
select {
case msg := <-msgCh:
handle(msg)
case <-ctx.Done():
return ctx.Err()
}
See references/concurrency-patterns.md for worker pools, fan-out/fan-in, pipelines, sync primitives, context propagation.
Testing
func TestParseSize(t *testing.T) {
tests := []struct {
name string
input string
want int64
wantErr bool
}{
{name: "bytes", input: "100B", want: 100},
{name: "kilobytes", input: "2KB", want: 2048},
{name: "empty", input: "", wantErr: true},
{name: "invalid", input: "abc", wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseSize(tt.input)
if tt.wantErr {
if err == nil {
t.Fatal("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != tt.want {
t.Errorf("ParseSize(%q) = %d, want %d", tt.input, got, tt.want)
}
})
}
}
func TestHealthHandler(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/health", nil)
w := httptest.NewRecorder()
HealthHandler(w, req)
if w.Code != http.StatusOK {
t.Errorf("status = %d, want %d", w.Code, http.StatusOK)
}
}
Coverage target: 80%+
go test -cover -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
go tool cover -html=coverage.out
See references/testing-go.md for subtests, benchmarks, fuzz testing, testify, httptest patterns, test helpers.
Tooling
Go Modules
go mod init github.com/org/project
go mod tidy
go mod vendor
go get github.com/pkg/errors@v0.9.1
golangci-lint (Linting)
Runs 50+ linters in parallel. Single tool replaces go vet, staticcheck, errcheck, gosec, and more.
linters:
enable:
- errcheck
- govet
- staticcheck
- unused
- gosimple
- ineffassign
- gocritic
- gosec
- errname
- exhaustive
linters-settings:
govet:
enable-all: true
gocritic:
enabled-tags: [diagnostic, style, performance]
issues:
exclude-use-default: false
max-issues-per-linter: 0
max-same-issues: 0
golangci-lint run ./...
See references/tooling-config.md for complete linter configs, Makefile patterns, CI setup, build tags.
Structs and Methods
type User struct {
ID string `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Email string `json:"email" db:"email"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
}
func NewUser(name, email string) *User {
return &User{
ID: uuid.NewString(),
Name: name,
Email: email,
CreatedAt: time.Now(),
}
}
func (u User) DisplayName() string {
return fmt.Sprintf("%s <%s>", u.Name, u.Email)
}
func (u *User) SetEmail(email string) error {
if !strings.Contains(email, "@") {
return &ValidationError{Field: "email", Message: "invalid format"}
}
u.Email = email
return nil
}
| Use | Receiver | Why |
|---|
| Read-only, small struct | Value (u User) | No mutation, safe copy |
| Mutates state | Pointer (u *User) | Changes visible to caller |
| Large struct (>3 fields) | Pointer (u *User) | Avoid copy overhead |
| Implements interface with pointer methods | Pointer (u *User) | Consistency required |
Project Structure
myproject/
├── cmd/
│ └── myapp/
│ └── main.go # entry point, wiring only
├── internal/ # private to this module
│ ├── server/ # HTTP server setup
│ ├── user/ # user domain logic
│ └── storage/ # database layer
├── pkg/ # importable by other projects (use sparingly)
├── go.mod
├── go.sum
├── Makefile
└── .golangci.yml
See references/project-structure.md for multi-binary repos, dependency injection, config patterns, build tags.
Anti-patterns Quick Reference
| Anti-pattern | Fix |
|---|
panic() for expected errors | Return error |
Ignoring errors _ = f() | Handle or explicitly document why safe |
interface{} / any everywhere | Use generics (1.18+) or specific types |
| Goroutine leak (no exit path) | Use context.Context + select |
| Shared state without sync | sync.Mutex, channels, or atomic |
init() with side effects | Explicit initialization in main() |
| Giant interfaces (>5 methods) | Split into focused 1-3 method interfaces |
| Package-level mutable state | Dependency injection |
| Premature channel/goroutine use | Start sequential, add concurrency when needed |
log.Fatal in library code | Return errors, let caller decide |
| Naked returns in long functions | Named returns only for short functions or godoc |
Missing defer for cleanup | defer f.Close() immediately after open |
Naming Conventions
| Element | Convention | Example |
|---|
| Package | short, lowercase, singular | user, http, json |
| Exported function | PascalCase, verb-noun | ParseConfig, NewServer |
| Unexported function | camelCase | validateInput, buildQuery |
| Interface (1 method) | Method + "er" | Reader, Stringer, Handler |
| Interface (multi) | Descriptive noun | UserStore, EventBus |
| Error variable | Err + description | ErrNotFound, ErrTimeout |
| Error type | Description + Error | ValidationError, TimeoutError |
| Constants | PascalCase (exported) or camelCase | MaxRetries, defaultTimeout |
| Acronyms | All caps | HTTPServer, userID, xmlParser |
Project Setup Checklist
go mod init github.com/org/project
mkdir -p cmd/myapp internal/{server,storage}
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go vet ./... && golangci-lint run ./... && go test -race -cover ./...
Commands
go build ./...
go test -v -race -cover ./...
go test -run TestSpecific ./internal/user/
go vet ./...
golangci-lint run ./...
go mod tidy
Reference Files
- references/concurrency-patterns.md - Goroutines, channels, select, errgroup, worker pools, pipelines
- references/error-handling.md - Wrapping, sentinel errors, custom types, panic/recover, strategies
- references/testing-go.md - Table-driven tests, subtests, benchmarks, fuzz, testify, httptest
- references/project-structure.md - Module layout, cmd/internal/pkg, DI, config, build tags
- references/tooling-config.md - golangci-lint, go vet, gofumpt, Makefile, CI/CD configs