Go
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.From its SKILL.md
npx -y skills add aj-geddes/unicorn-team --skill goAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 14 stars14 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
11.5 KB, as published. Nobody here has run it
Go Domain Skill
Error Handling
Go uses explicit error returns, not exceptions. Every error is a value you handle at the call site.
// Return errors, don't panic
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
}
// Sentinel errors for callers to check
var (
ErrNotFound = errors.New("not found")
ErrForbidden = errors.New("forbidden")
)
// Custom error types for rich context
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation: %s: %s", e.Field, e.Message)
}
// Callers use errors.Is / errors.As
if errors.Is(err, ErrNotFound) { /* handle */ }
var ve *ValidationError
if errors.As(err, &ve) { /* access ve.Field */ }
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.
// Small interfaces at the consumer site
type UserStore interface {
GetUser(ctx context.Context, id string) (*User, error)
}
// Accept interfaces, return structs
func NewUserService(store UserStore) *UserService {
return &UserService{store: store}
}
// Common stdlib interfaces to know
// io.Reader, io.Writer -- streaming data
// fmt.Stringer -- string representation
// sort.Interface -- custom sorting
// encoding.BinaryMarshaler -- serialization
// context.Context -- cancellation and deadlines
// http.Handler -- HTTP request handling
| 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
// Goroutines + channels for concurrent work
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 // safe: each goroutine owns its index
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
// Context for cancellation and timeouts
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
// Select for multiplexing channels
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
// Table-driven tests -- the Go standard
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)
}
})
}
}
// HTTP handler testing
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 # summary
go tool cover -html=coverage.out # visual report
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 # initialize
go mod tidy # sync deps
go mod vendor # vendored deps (optional)
go get github.com/pkg/[email protected] # add/update dep
golangci-lint (Linting)
Runs 50+ linters in parallel. Single tool replaces go vet, staticcheck, errcheck, gosec, and more.
# .golangci.yml
linters:
enable:
- errcheck # unchecked errors
- govet # suspicious constructs
- staticcheck # advanced analysis
- unused # unused code
- gosimple # simplifications
- ineffassign # ineffective assignments
- gocritic # opinionated checks
- gosec # security issues
- errname # error naming conventions
- exhaustive # enum exhaustiveness
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
// Struct with tags for serialization
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"`
}
// Constructor function (not a Go "constructor" -- just convention)
func NewUser(name, email string) *User {
return &User{
ID: uuid.NewString(),
Name: name,
Email: email,
CreatedAt: time.Now(),
}
}
// Value receiver: doesn't modify receiver, safe on copies
func (u User) DisplayName() string {
return fmt.Sprintf("%s <%s>", u.Name, u.Email)
}
// Pointer receiver: modifies receiver or is large
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
# 1. Initialize module
go mod init github.com/org/project
# 2. Create directory structure
mkdir -p cmd/myapp internal/{server,storage}
# 3. Install tooling
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# 4. Create .golangci.yml (see Tooling section)
# 5. Create Makefile
# make build, make test, make lint, make run
# 6. Full quality check
go vet ./... && golangci-lint run ./... && go test -race -cover ./...
Commands
go build ./... # Build all packages
go test -v -race -cover ./... # Test with race detector + coverage
go test -run TestSpecific ./internal/user/ # Run specific test
go vet ./... # Static analysis
golangci-lint run ./... # Lint (50+ linters)
go mod tidy # Sync dependencies
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