golang
Installation
SKILL.md
When to Use
- Writing new Go code or packages
- Refactoring existing Go code
- Implementing TDD workflow in Go
- Reviewing Go code for best practices
- Designing package structure and APIs
Project Structure
project/
├── cmd/ # Application entrypoints
│ └── myapp/
│ └── main.go
├── internal/ # Private application code
│ ├── domain/
│ ├── service/
│ └── repository/
├── pkg/ # Public reusable packages
├── api/ # API definitions (OpenAPI, proto)
├── configs/ # Configuration files
├── scripts/ # Build/deploy scripts
├── go.mod
├── go.sum
└── Makefile
Critical Patterns
1. Error Handling
// GOOD: Wrap errors with context
func (s *UserService) GetUser(ctx context.Context, id string) (*User, error) {
user, err := s.repo.FindByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("get user %s: %w", id, err)
}
return user, nil
}
// GOOD: Define sentinel errors
var (
ErrNotFound = errors.New("not found")
ErrUnauthorized = errors.New("unauthorized")
)
// GOOD: Check for specific errors
if errors.Is(err, ErrNotFound) {
// handle not found
}
2. Interface Design
// GOOD: Small, focused interfaces (accept interfaces, return structs)
type Reader interface {
Read(ctx context.Context, id string) (*Entity, error)
}
type Writer interface {
Write(ctx context.Context, entity *Entity) error
}
// Compose when needed
type ReadWriter interface {
Reader
Writer
}
// GOOD: Define interfaces where they're used, not where implemented
// In service package:
type userRepository interface {
FindByID(ctx context.Context, id string) (*User, error)
}
type UserService struct {
repo userRepository // lowercase = private, defined here
}
3. Constructor Pattern
// GOOD: Functional options for complex constructors
type Server struct {
addr string
timeout time.Duration
logger Logger
}
type Option func(*Server)
func WithTimeout(d time.Duration) Option {
return func(s *Server) {
s.timeout = d
}
}
func WithLogger(l Logger) Option {
return func(s *Server) {
s.logger = l
}
}
func NewServer(addr string, opts ...Option) *Server {
s := &Server{
addr: addr,
timeout: 30 * time.Second, // default
logger: noopLogger{}, // default
}
for _, opt := range opts {
opt(s)
}
return s
}
// Usage
server := NewServer(":8080", WithTimeout(time.Minute), WithLogger(myLogger))
4. Context Usage
// GOOD: Context as first parameter
func (s *Service) DoSomething(ctx context.Context, input Input) error {
// Check for cancellation
select {
case <-ctx.Done():
return ctx.Err()
default:
}
// Pass context to downstream calls
return s.repo.Save(ctx, input)
}
// BAD: Don't store context in structs
type Service struct {
ctx context.Context // NEVER do this
}
TDD Workflow
Red-Green-Refactor Cycle
1. RED: Write a failing test first
2. GREEN: Write minimal code to pass
3. REFACTOR: Improve code, keep tests green
Test Structure (AAA Pattern)
func TestUserService_GetUser(t *testing.T) {
// Arrange
repo := &mockUserRepository{
user: &User{ID: "123", Name: "John"},
}
svc := NewUserService(repo)
// Act
user, err := svc.GetUser(context.Background(), "123")
// Assert
require.NoError(t, err)
assert.Equal(t, "John", user.Name)
}
Table-Driven Tests
func TestCalculate(t *testing.T) {
tests := []struct {
name string
input int
expected int
wantErr bool
}{
{
name: "positive number",
input: 5,
expected: 25,
wantErr: false,
},
{
name: "zero",
input: 0,
expected: 0,
wantErr: false,
},
{
name: "negative number",
input: -1,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := Calculate(tt.input)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, tt.expected, result)
})
}
}
Test Doubles
// Mock for testing
type mockRepository struct {
findFn func(ctx context.Context, id string) (*Entity, error)
saveFn func(ctx context.Context, e *Entity) error
}
func (m *mockRepository) Find(ctx context.Context, id string) (*Entity, error) {
if m.findFn != nil {
return m.findFn(ctx, id)
}
return nil, nil
}
// Usage in test
repo := &mockRepository{
findFn: func(ctx context.Context, id string) (*Entity, error) {
return &Entity{ID: id}, nil
},
}
Testing HTTP Handlers
func TestHandler_GetUser(t *testing.T) {
// Arrange
svc := &mockUserService{
user: &User{ID: "123", Name: "John"},
}
handler := NewHandler(svc)
req := httptest.NewRequest(http.MethodGet, "/users/123", nil)
rec := httptest.NewRecorder()
// Act
handler.GetUser(rec, req)
// Assert
assert.Equal(t, http.StatusOK, rec.Code)
var response User
err := json.NewDecoder(rec.Body).Decode(&response)
require.NoError(t, err)
assert.Equal(t, "John", response.Name)
}
Code Style
Naming Conventions
| Type | Convention | Example |
|---|---|---|
| Package | lowercase, single word | user, order |
| Interface | -er suffix for single method | Reader, Writer |
| Exported | PascalCase | GetUser, UserService |
| Unexported | camelCase | getUserByID, userRepo |
| Acronyms | Consistent case | HTTPServer, userID |
| Test files | _test.go suffix |
user_test.go |
Documentation
// Package user provides user management functionality.
package user
// User represents a system user with authentication credentials.
type User struct {
ID string
Email string
}
// GetByID retrieves a user by their unique identifier.
// It returns ErrNotFound if no user exists with the given ID.
func (s *Service) GetByID(ctx context.Context, id string) (*User, error) {
// ...
}
Anti-Patterns to Avoid
| Anti-Pattern | Problem | Solution |
|---|---|---|
interface{} / any overuse |
Loses type safety | Use generics or specific types |
| Naked returns | Hard to read | Use explicit returns |
init() functions |
Hidden side effects | Use explicit initialization |
| Global state | Hard to test | Use dependency injection |
| Panic for errors | Crashes program | Return errors instead |
| Ignoring errors | Silent failures | Always handle or wrap errors |
Commands
# Run tests
go test ./...
# Run tests with coverage
go test -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
# Run tests verbose
go test -v ./...
# Run specific test
go test -run TestUserService_GetUser ./...
# Run benchmarks
go test -bench=. ./...
# Lint code
golangci-lint run
# Format code
gofmt -w .
goimports -w .
# Vet code
go vet ./...
# Generate mocks (with mockgen)
mockgen -source=repository.go -destination=mock_repository.go -package=user
# Build
go build -o bin/myapp ./cmd/myapp
# Tidy dependencies
go mod tidy
Useful Test Assertions (testify)
import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// assert - continues on failure
assert.Equal(t, expected, actual)
assert.NotNil(t, value)
assert.True(t, condition)
assert.Contains(t, slice, element)
assert.Empty(t, collection)
// require - stops on failure
require.NoError(t, err)
require.NotNil(t, value)
Resources
- Style Guide: Effective Go
- Code Review: Go Code Review Comments
- Project Layout: Standard Go Project Layout
Related skills