Go Best Practices
Apply comprehensive Go development best practices covering code organization, performance optimization, security, testing, and common patterns. This skill ensures idiomatic Go code that follows community standards and production-ready practices.
Instructions
When working with Go code, follow these guidelines:
1. Code Organization and Structure
**Directory Structure:**
Use standard Go project layout with `cmd/`, `internal/`, `pkg/`, `api/`, `web/`, `scripts/`, and `configs/` directoriesPlace application entry points in `cmd/project-name/main.go`Keep private code in `internal/` (app logic, domain models, internal packages)Put reusable libraries in `pkg/` for external consumptionStore API definitions (protobuf, OpenAPI) in `api/`Use lowercase snake_case for file names (e.g., `user_service.go`)Name test files with `_test.go` suffix**Module Organization:**
Use Go modules for dependency managementFollow semantic versioning (SemVer) for releasesConsider vendoring for critical applications using `go mod vendor`**Architecture:**
Apply layered or clean architecture principlesUse dependency injection to decouple componentsDefine clear interfaces between layersKeep packages small, focused, and single-responsibilityUse functional options pattern for functions with many optional parameters2. Common Patterns
**Design Patterns:**
Factory pattern for complex object creationStrategy pattern for interchangeable algorithmsObserver pattern for event-driven systemsContext pattern: Pass `context.Context` as first argument to I/O or long-running operationsMiddleware pattern for HTTP request processing chains**Recommended Approaches:**
**Configuration:** Use `spf13/viper` or `joho/godotenv` for config management**Logging:** Use structured logging with `sirupsen/logrus` or `uber-go/zap`**Database:** Use `database/sql` with prepared statements; consider `gorm.io/gorm` for complex interactions**HTTP:** Use `net/http` or frameworks like `gin-gonic/gin` or `go-chi/chi` with proper timeouts**Async Tasks:** Use goroutines and channels; synchronize with wait groups**Validation:** Use `go-playground/validator` and sanitize user input**Anti-patterns to Avoid:**
Never ignore errors; always handle explicitlyAvoid `panic` for normal error handlingMinimize global variablesAvoid variable shadowingDon't overuse goroutines; use worker pools for concurrency controlAvoid mutable global stateReplace magic numbers/strings with named constantsKeep functions short and focusedAvoid deeply nested code; use early returns**Error Handling:**
Always check and handle errors explicitlyWrap errors with context using `fmt.Errorf` with `%w` verbDefine custom error types for specific conditionsUse sentinel errors (`var ErrNotFound = errors.New("not found")`) for simple casesUse `defer` for resource cleanupConsider `go.uber.org/multierr` for multiple errors3. Performance
**Optimization:**
Profile with `pprof` to identify bottlenecksBenchmark critical code with `testing` packageUse appropriate data structures (`sync.Map` for concurrent maps)Use `strings.Builder` for string concatenation in loopsMinimize memory allocations; reuse buffers and objectsUnderstand escape analysis to reduce heap allocationsImplement caching strategies (in-memory or distributed)**Memory Management:**
Be aware of garbage collection behaviorPrefer stack allocation over heap when possibleUse object pooling for frequently created/destroyed objectsUnderstand slice vs. array trade-offsBe mindful of data copying; use pointers when appropriate4. Security
**Best Practices:**
Validate and sanitize all user inputUse prepared statements to prevent SQL injectionAvoid storing sensitive data in plain textUse HTTPS for all network communicationImplement authentication and authorization properlyKeep dependencies updated; use `go list -m -u all`Follow principle of least privilegeUse secure random number generation (`crypto/rand`)Implement rate limiting and request throttlingSanitize error messages to avoid information leakage5. Testing
**Testing Strategy:**
Write unit tests for all functionsUse table-driven tests for multiple scenariosAim for high code coverage (80%+ minimum)Use mocking for external dependencies (`golang/mock` or `stretchr/testify`)Write integration tests for critical pathsUse `testing.T.Helper()` for test helper functionsRun tests with race detector: `go test -race`**Example table-driven test:**
```go
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
want int
}{
{"positive", 1, 2, 3},
{"negative", -1, -2, -3},
{"zero", 0, 0, 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Add(tt.a, tt.b); got != tt.want {
t.Errorf("Add() = %v, want %v", got, tt.want)
}
})
}
}
```
6. Concurrency
**Goroutines and Channels:**
Use goroutines for concurrent operationsCommunicate via channels instead of shared memoryUse buffered channels when appropriateAvoid unbuffered channels that can cause deadlocksUse `sync.WaitGroup` to synchronize goroutinesUse `sync.Mutex` or `sync.RWMutex` for shared statePrefer `sync/atomic` for simple atomic operationsAlways handle channel closure properlyUse `select` for channel multiplexingImplement proper cancellation with `context.Context`7. Code Style
**Formatting and Conventions:**
Run `gofmt` or `goimports` on all codeFollow effective Go guidelinesUse meaningful variable and function namesWrite clear comments for exported functions and typesKeep line length reasonable (80-120 characters)Group imports: standard library, third-party, localUse blank lines to separate logical sectionsAvoid stuttering in names (`user.UserID` → `user.ID`)8. Documentation
**Code Documentation:**
Document all exported functions, types, and packagesStart comments with the name of the element being documentedUse complete sentences in commentsProvide usage examples for complex APIsMaintain README.md with setup and usage instructionsDocument configuration options and environment variablesExamples
**Proper error handling:**
```go
func readFile(filename string) ([]byte, error) {
data, err := ioutil.ReadFile(filename)
if err != nil {
return nil, fmt.Errorf("failed to read file %s: %w", filename, err)
}
return data, nil
}
```
**Functional options pattern:**
```go
type Server struct {
Addr string
Port int
Timeout time.Duration
}
type Option func(*Server)
func WithPort(port int) Option {
return func(s *Server) { s.Port = port }
}
func NewServer(options ...Option) *Server {
srv := &Server{Addr: "localhost", Port: 8080, Timeout: 30 * time.Second}
for _, option := range options {
option(srv)
}
return srv
}
```
**Context usage:**
```go
func handleRequest(ctx context.Context, req *http.Request) {
select {
case <-ctx.Done():
return // Operation cancelled
default:
// Process the request
}
}
```
Constraints
Always handle errors explicitly; never ignore themUse context for cancellation and timeouts in I/O operationsPrefer composition over inheritanceWrite tests alongside production codeRun `go vet` and `golint` before committingKeep functions under 50 lines when possibleLimit cyclomatic complexity to 10 or less per functionUse interfaces for abstraction, not concrete types