golang-conventions
1
总安装量
1
周安装量
#54246
全站排名
安装命令
npx skills add https://github.com/rory-data/copilot --skill golang-conventions
Agent 安装分布
amp
1
opencode
1
kimi-cli
1
codex
1
claude-code
1
Skill 文档
Go Coding Conventions
Quick Reference
- Go version: Use Go 1.21+ for modern features (generics, slices package, cmp)
- Formatting: Use
gofmtandgoimports(automatic via gopls) - Linting: Use
golangci-lintwith comprehensive linters enabled - Testing: Table-driven tests, test fixtures, subtests with
t.Run() - Error handling: Explicit, descriptive errors with
errors.Is()anderrors.As()
Core Principles
1. Simplicity and Clarity
- Write clear, idiomatic Go code
- Prefer simple solutions over clever ones
- Follow the principle of least surprise
- “Clear is better than clever” – Go Proverbs
2. Explicit Over Implicit
- No hidden control flow
- Explicit error handling (no exceptions)
- Clear dependency management
- Visible type conversions
3. Composition Over Inheritance
- Use interfaces for abstraction
- Prefer struct embedding over inheritance
- Accept interfaces, return structs
- Keep interfaces small and focused
Code Style and Formatting
Naming Conventions
// Package names: lowercase, single word, no underscores
package user
// Exported identifiers: PascalCase
type UserService struct {}
func NewUserService() *UserService {}
// Unexported identifiers: camelCase
type userData struct {}
func validateEmail(email string) error {}
// Interface names: single method interfaces end with -er
type Reader interface { Read(p []byte) (n int, err error) }
type UserRepository interface { /* multiple methods */ }
// Acronyms: consistent case (HTTP, ID, URL, not Http, Id, Url)
type HTTPServer struct {}
var userID int64
File Organisation
// 1. Package declaration
package user
// 2. Import statements (grouped: stdlib, external, internal)
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
"myapp/internal/database"
)
// 3. Constants
const (
MaxRetries = 3
DefaultTimeout = 30 * time.Second
)
// 4. Variables (avoid package-level vars, prefer const or functions)
var (
ErrUserNotFound = errors.New("user not found")
)
// 5. Types
type User struct {
ID uuid.UUID
Email string
CreatedAt time.Time
}
// 6. Functions and methods (exported first, then unexported)
Modern Go Features (1.21+)
// Generics for type-safe collections
func Map[T, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}
// min/max built-ins
func clamp(val, minVal, maxVal int) int {
return max(minVal, min(val, maxVal))
}
// clear() built-in for maps and slices
func resetCache(cache map[string]any) {
clear(cache)
}
// slices package for common operations
import "slices"
sorted := slices.Clone(original)
slices.Sort(sorted)
Error Handling
Explicit and Descriptive
// Good: Clear error handling
func GetUser(ctx context.Context, id uuid.UUID) (*User, error) {
user, err := db.FindUserByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to get user %s: %w", id, err)
}
return user, nil
}
// Use errors.Is() and errors.As() for sentinel errors
if errors.Is(err, ErrUserNotFound) {
return handleNotFound()
}
var validationErr *ValidationError
if errors.As(err, &validationErr) {
return handleValidation(validationErr)
}
// Custom error types for rich error information
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
Error Wrapping
// Wrap errors to preserve context
func processUser(ctx context.Context, id uuid.UUID) error {
user, err := GetUser(ctx, id)
if err != nil {
return fmt.Errorf("process user failed: %w", err)
}
if err := validateUser(user); err != nil {
return fmt.Errorf("validation failed for user %s: %w", id, err)
}
return nil
}
Testing
Table-Driven Tests
func TestValidateEmail(t *testing.T) {
tests := []struct {
name string
email string
wantErr bool
}{
{
name: "valid email",
email: "user@example.com",
wantErr: false,
},
{
name: "missing at sign",
email: "userexample.com",
wantErr: true,
},
{
name: "empty email",
email: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateEmail(tt.email)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateEmail() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
Test Fixtures and Helpers
// testdata/ directory for fixtures
// helper_test.go for test utilities
func setupTestDB(t *testing.T) *sql.DB {
t.Helper()
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("failed to open test db: %v", err)
}
t.Cleanup(func() {
db.Close()
})
return db
}
// Use testify for assertions (optional but popular)
import "github.com/stretchr/testify/assert"
func TestUserCreation(t *testing.T) {
user := NewUser("test@example.com")
assert.NotNil(t, user.ID)
assert.Equal(t, "test@example.com", user.Email)
assert.False(t, user.CreatedAt.IsZero())
}
Benchmarks and Examples
// Benchmark functions
func BenchmarkValidateEmail(b *testing.B) {
email := "user@example.com"
b.ResetTimer()
for i := 0; i < b.N; i++ {
ValidateEmail(email)
}
}
// Example functions (appear in godoc)
func ExampleValidateEmail() {
err := ValidateEmail("user@example.com")
fmt.Println(err == nil)
// Output: true
}
Project Structure
Standard Layout
myproject/
âââ cmd/ # Main applications
â âââ server/
â âââ main.go
âââ internal/ # Private application code
â âââ user/
â â âââ user.go
â â âââ user_test.go
â â âââ repository.go
â â âââ service.go
â âââ database/
â âââ postgres.go
âââ pkg/ # Public libraries (optional)
â âââ utils/
âââ api/ # API definitions (OpenAPI, protobuf)
âââ web/ # Web assets (if applicable)
âââ scripts/ # Build and automation scripts
âââ deployments/ # Deployment configs (Docker, k8s)
âââ test/ # Additional test data
âââ go.mod
âââ go.sum
âââ Makefile # Common tasks
âââ README.md
Package Organisation
// internal/user/user.go - Domain types
package user
type User struct {
ID uuid.UUID
Email string
}
// internal/user/repository.go - Data access
package user
type Repository interface {
Create(ctx context.Context, user *User) error
FindByID(ctx context.Context, id uuid.UUID) (*User, error)
}
// internal/user/service.go - Business logic
package user
type Service struct {
repo Repository
}
func NewService(repo Repository) *Service {
return &Service{repo: repo}
}
Dependency Management
Go Modules Best Practices
# Initialise module
go mod init github.com/username/project
# Add dependencies (automatically)
go get github.com/google/uuid@latest
# Tidy up (remove unused, add missing)
go mod tidy
# Verify integrity
go mod verify
# Vendor dependencies (optional)
go mod vendor
Version Pinning
// go.mod with specific versions
module github.com/username/project
go 1.21
require (
github.com/google/uuid v1.5.0
github.com/lib/pq v1.10.9
)
require (
// Indirect dependencies
golang.org/x/crypto v0.17.0 // indirect
)
Concurrency
Goroutines and Channels
// Use context for cancellation
func processItems(ctx context.Context, items []Item) error {
results := make(chan Result, len(items))
errors := make(chan error, 1)
for _, item := range items {
go func(item Item) {
select {
case <-ctx.Done():
return
case results <- processItem(item):
}
}(item)
}
// Collect results
for i := 0; i < len(items); i++ {
select {
case <-ctx.Done():
return ctx.Err()
case err := <-errors:
return err
case result := <-results:
// Handle result
}
}
return nil
}
sync Package Patterns
import "sync"
// Use sync.WaitGroup for coordinating goroutines
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
go func(item Item) {
defer wg.Done()
process(item)
}(item)
}
wg.Wait()
// Use sync.Once for one-time initialisation
var (
instance *Service
once sync.Once
)
func GetService() *Service {
once.Do(func() {
instance = newService()
})
return instance
}
// Use sync.Pool for frequently allocated objects
var bufferPool = sync.Pool{
New: func() any {
return new(bytes.Buffer)
},
}
func processData(data []byte) {
buf := bufferPool.Get().(*bytes.Buffer)
defer bufferPool.Put(buf)
buf.Reset()
buf.Write(data)
// Process buffer
}
Performance Best Practices
Memory Optimisation
// Preallocate slices when size is known
items := make([]Item, 0, expectedCount)
// Use pointers for large structs in maps
type Cache map[string]*LargeStruct
// Reuse buffers
var buf bytes.Buffer
buf.Reset() // Clear for reuse
// Use string builder for concatenation
var sb strings.Builder
for _, s := range strings {
sb.WriteString(s)
}
result := sb.String()
Profiling
import (
"runtime/pprof"
"net/http"
_ "net/http/pprof"
)
// CPU profiling
f, _ := os.Create("cpu.prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
// Memory profiling
f, _ := os.Create("mem.prof")
pprof.WriteHeapProfile(f)
// HTTP profiling endpoint
go func() {
http.ListenAndServe("localhost:6060", nil)
}()
Linting and Quality
golangci-lint Configuration
# .golangci.yml
run:
timeout: 5m
go: "1.21"
linters:
enable:
- errcheck # Check error handling
- gosimple # Simplify code
- govet # Examine Go source code
- ineffassign # Detect ineffectual assignments
- staticcheck # Advanced static analysis
- unused # Find unused code
- gofmt # Format checking
- goimports # Import management
- misspell # Spell checking
- revive # Fast, configurable linter
- gocritic # Comprehensive diagnostics
- gosec # Security issues
- errname # Error naming conventions
- errorlint # Error wrapping
- exhaustive # Enum switch exhaustiveness
linters-settings:
errcheck:
check-blank: true
govet:
check-shadowing: true
gofmt:
simplify: true
revive:
rules:
- name: unexported-return
disabled: true
Documentation
Package Documentation
// Package user provides user management functionality.
//
// It includes user creation, validation, and repository patterns
// for data persistence. All operations support context-based
// cancellation and timeout.
package user
// User represents a system user with authentication credentials.
//
// Email must be unique and validated before persistence.
// Passwords are stored using bcrypt hashing.
type User struct {
ID uuid.UUID `json:"id"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
}
// NewUser creates a User with a generated UUID and current timestamp.
//
// Email validation is performed during persistence, not at creation.
func NewUser(email string) *User {
return &User{
ID: uuid.New(),
Email: email,
CreatedAt: time.Now(),
}
}
Comments
// Good: Explain WHY, not WHAT
// Hash password using bcrypt to protect against rainbow table attacks
hashedPassword, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
// Bad: Restating the code
// Hash the password
hashedPassword, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
// Use godoc comments for exported identifiers
// Use regular comments for implementation details
Common Patterns
Constructor Pattern
// NewService creates a Service with required dependencies.
func NewService(repo Repository, logger *slog.Logger) *Service {
return &Service{
repo: repo,
logger: logger,
}
}
// With options pattern for complex constructors
type Option func(*Service)
func WithCache(cache Cache) Option {
return func(s *Service) {
s.cache = cache
}
}
func NewServiceWithOptions(repo Repository, opts ...Option) *Service {
s := &Service{repo: repo}
for _, opt := range opts {
opt(s)
}
return s
}
Interface Segregation
// Good: Small, focused interfaces
type Reader interface {
Read(ctx context.Context, id string) (*Data, error)
}
type Writer interface {
Write(ctx context.Context, data *Data) error
}
type ReadWriter interface {
Reader
Writer
}
// Accept interfaces, return structs
func ProcessData(r Reader, id string) (*Result, error) {
data, err := r.Read(context.Background(), id)
// ...
}
Middleware Pattern
type HandlerFunc func(http.ResponseWriter, *http.Request)
type Middleware func(HandlerFunc) HandlerFunc
func LoggingMiddleware(logger *slog.Logger) Middleware {
return func(next HandlerFunc) HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next(w, r)
logger.Info("request",
"method", r.Method,
"path", r.URL.Path,
"duration", time.Since(start),
)
}
}
}
// Chain middlewares
func Chain(h HandlerFunc, middlewares ...Middleware) HandlerFunc {
for i := len(middlewares) - 1; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}
Build and Deployment
Makefile
.PHONY: build test lint clean
# Build binary
build:
go build -o bin/server cmd/server/main.go
# Run tests
test:
go test -v -race -coverprofile=coverage.out ./...
# Run linter
lint:
golangci-lint run ./...
# Format code
fmt:
gofmt -s -w .
goimports -w .
# Clean build artifacts
clean:
rm -rf bin/ coverage.out
# Install tools
tools:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install golang.org/x/tools/cmd/goimports@latest
Build Tags
//go:build integration
// +build integration
package user_test
// Integration tests only run with: go test -tags=integration
Anti-Patterns to Avoid
â Don’t do this:
// Panic in library code (use errors)
func GetUser(id string) *User {
user := findUser(id)
if user == nil {
panic("user not found")
}
return user
}
// Ignoring errors
result, _ := doSomething()
// Naked returns in long functions
func calculate(a, b int) (result int) {
// ... 50 lines of code ...
return
}
// Global mutable state
var users = make(map[string]*User)
â Do this instead:
// Return errors explicitly
func GetUser(id string) (*User, error) {
user := findUser(id)
if user == nil {
return nil, ErrUserNotFound
}
return user, nil
}
// Handle all errors
result, err := doSomething()
if err != nil {
return fmt.Errorf("operation failed: %w", err)
}
// Explicit returns
func calculate(a, b int) int {
result := a + b
return result
}
// Dependency injection
type UserService struct {
users map[string]*User
}
Tools and Ecosystem
Essential Tools
# Format and imports
go install golang.org/x/tools/cmd/goimports@latest
# Linting
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# Testing utilities
go install github.com/stretchr/testify@latest
# Code generation (if needed)
go install github.com/golang/mock/mockgen@latest
# Hot reload for development
go install github.com/cosmtrek/air@latest
IDE Configuration
// VSCode settings.json
{
"go.useLanguageServer": true,
"go.lintTool": "golangci-lint",
"go.lintOnSave": "package",
"go.formatTool": "goimports",
"editor.formatOnSave": true,
"go.testFlags": ["-v", "-race"],
"go.coverOnSave": true
}
Security Best Practices
// Use crypto/rand for random values
import "crypto/rand"
func generateToken() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
// Validate and sanitise inputs
func sanitiseUserInput(input string) string {
return html.EscapeString(strings.TrimSpace(input))
}
// Use prepared statements for SQL
stmt, err := db.PrepareContext(ctx, "SELECT * FROM users WHERE id = $1")
defer stmt.Close()
// Set timeouts on contexts
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
Alignment with Core Principles
- SOLID Principles: Single responsibility through package design, dependency inversion via interfaces
- DRY: Avoid duplication through proper abstraction and generics
- YAGNI: Build what you need now, Go’s simplicity encourages this
- KISS: Go’s design philosophy emphasises simplicity
- Test Pyramid: 70% unit (table-driven), 20% integration (with build tags), 10% e2e