- name
- golang
- version
- 2.0.0
- description
- Use when building Go backend services, implementing goroutines/channels, handling errors idiomatically, writing tests with testify, or following Go best practices for APIs/CLI tools.
- keywords
- ["Go","Golang","concurrency","goroutines","channels","error handling","testing","backend","API"]
- plugin
- dev
- updated
- 2026-01-20T00:00:00.000Z
# Go Development Best Practices
**Version**: 2.0.0
**Purpose**: Comprehensive Go development patterns covering idioms, error handling, concurrency, testing, and quality
**Scope**: Backend development with Go - API services, CLI tools, system software
**Prerequisites**: Basic Go syntax knowledge
## Overview
Go (Golang) is designed for simplicity, explicit error handling, and safe concurrent programming. This skill covers production-ready patterns validated by the Go community, official documentation, and industry standards (Uber Engineering, Google).
**Core Philosophy**:
- **Simplicity**: "Clear is better than clever" - favor readable code over abstractions
- **Explicit over implicit**: No exceptions, no hidden control flow, visible errors
- **Composition over inheritance**: Interfaces and embedding, not class hierarchies
- **Built-in concurrency**: Goroutines and channels as first-class primitives
- **Tooling-first**: Format, vet, test, and benchmark built into the language
**Key Design Principles**:
1. Small interfaces (1-3 methods ideal)
2. Consumer-side interface placement
3. Error values, not exceptions
4. Happy path at left margin
5. Goroutines must have explicit termination
---
## 1. Idiomatic Go Patterns
### 1.1 Naming Conventions
**Package Names**:
```go
// ✅ GOOD: Package names are single lowercase identifiers
// Import path: "net/url" → package name: url
// Import path: "encoding/json" → package name: json
package url // from "net/url"
package json // from "encoding/json"
package strings
// ❌ BAD
package urls // No plural
package encodingjson // Don't smash words together
package stringutils // Too verbose
```
**Getters and Setters**:
```go
type Account struct {
balance int
}
// ✅ GOOD: No "Get" prefix
func (a *Account) Balance() int {
return a.balance
}
func (a *Account) SetBalance(amount int) {
a.balance = amount
}
// ❌ BAD: Java-style getters
func (a *Account) GetBalance() int {
return a.balance
}
```
**Error Variables**:
```go
// Exported sentinel errors (capitalized)
var ErrNotFound = errors.New("not found")
var ErrTimeout = errors.New("timeout")
// Unexported internal errors (lowercase)
var errInternal = errors.New("internal error")
```
**Interface Naming**:
```go
// ✅ GOOD: Short, descriptive
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// ❌ BAD: Verbose or unclear
type DataReader interface { ... }
type IReader interface { ... } // No "I" prefix
```
---
### 1.2 Interface Design - "The Bigger the Interface, the Weaker the Abstraction"
**Core Principle**: Small, consumer-side interfaces provide maximum flexibility.
**Single-Method Interfaces** (Ideal):
```go
// Standard library examples
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// Compose interfaces
type ReadCloser interface {
Reader
Closer
}
```
**Consumer-Side Interface Placement**:
```go
// ❌ WRONG: Producer defines interface
package store
type CustomerStorage interface {
StoreCustomer(Customer) error
GetCustomer(string) (Customer, error)
UpdateCustomer(Customer) error
// 10+ methods...
}
type PostgresStore struct {}
func (s *PostgresStore) StoreCustomer(...) { ... }
// ✅ CORRECT: Consumer defines what it needs
package client
type customerGetter interface {
GetCustomer(string) (store.Customer, error)
}
func ProcessCustomer(cg customerGetter) {
customer, _ := cg.GetCustomer("123")
// Only depends on GetCustomer method
}
```
**Return Concrete Types, Accept Interfaces** (Postel's Law):
```go
// ✅ GOOD
func NewStore() *PostgresStore {
return &PostgresStore{}
}
func Process(storage CustomerStorage) error {
// Accepts interface
}
// ❌ BAD: Returning interface
func NewStore() CustomerStorage {
return &PostgresStore{}
}
```
**When to Create Interfaces**:
- Multiple implementations exist or are planned
- Need for testing (mocking dependencies)
- Decoupling packages
- **NOT for**: Single implementation with no testing need
---
### 1.3 Happy Path Left, Early Returns
**Core Principle**: Align success path to left margin, handle errors first.
```go
// ❌ BAD: Deep nesting
func join(s1, s2 string, max int) (string, error) {
if s1 == "" {
return "", errors.New("s1 is empty")
} else {
if s2 == "" {
return "", errors.New("s2 is empty")
} else {
concat, err := concatenate(s1, s2)
if err != nil {
return "", err
} else {
if len(concat) > max {
return concat[:max], nil
} else {
return concat, nil
}
}
}
}
}
// ✅ GOOD: Happy path aligned left
func join(s1, s2 string, max int) (string, error) {
if s1 == "" {
return "", errors.New("s1 is empty")
}
if s2 == "" {
return "", errors.New("s2 is empty")
}
concat, err := concatenate(s1, s2)
if err != nil {
return "", err
}
if len(concat) > max {
return concat[:max], nil
}
return concat, nil
}
```
**Guidelines**:
- Maximum 3-4 levels of nesting
- Omit `else` blocks when `if` returns
- Handle errors immediately
- Keep normal flow at lowest indentation
---
### 1.4 Composition Over Inheritance
**Type Embedding** (Struct Composition):
```go
// Embedding for method promotion
type Logger struct {
*log.Logger
prefix string
}
func NewLogger(prefix string) *Logger {
return &Logger{
Logger: log.New(os.Stdout, "", 0),
prefix: prefix,
}
}
// Logger methods automatically available
logger := NewLogger("APP")
logger.Println("message") // Calls embedded log.Logger.Println
```
**Interface Composition**:
```go
type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// Compose interfaces
type ReadCloser interface {
Reader
Closer
}
```
**Warning**: Avoid embedding in public APIs:
```go
// ❌ BAD: Exposes implementation details
type MyHandler struct {
http.Handler // Leaks all Handler methods
}
// ✅ GOOD: Explicit delegation
type MyHandler struct {
handler http.Handler
}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Custom logic
h.handler.ServeHTTP(w, r)
}
```
---
### 1.5 Key Go Idioms
**Defer for Cleanup**:
```go
func processFile(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // Guaranteed cleanup
// Multiple returns, all close file
if condition {
return nil // File closed
}
return process(f) // File closed
}
// Mutex pattern
func (c *Counter) Increment() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++ // All paths unlock
}
```
**Critical Rule**: Call `defer` AFTER checking error:
```go
// ❌ WRONG
defer f.Close() // f is nil if Open failed
f, err := os.Open(path)
// ✅ CORRECT
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
```
**Multiple Return Values**:
```go
// (value, error) - Standard error handling
func GetUser(id string) (*User, error) {
// ...
}
// (value, bool) - "comma ok" idiom
value, ok := myMap[key]
if !ok {
// key not found
}
result, ok := someValue.(TargetType)
if !ok {
// type assertion failed
}
data, ok := <-channel
if !ok {
// channel closed
}
```
**Blank Identifier `_`**:
```go
// Ignore unwanted values
_, err := os.Open(filename)
// Compile-time interface check
var _ http.Handler = (*MyHandler)(nil)
// Import for side effects
import _ "net/http/pprof"
```
**Useful Zero Values**:
```go
// sync.Mutex - ready to use
var mu sync.Mutex
mu.Lock() // Works immediately
// bytes.Buffer - valid empty buffer
var buf bytes.Buffer
buf.WriteString("hello") // No initialization needed
// Slices - safe to read
var s []int
fmt.Println(len(s)) // 0 (safe)
```
---
## 2. Error Handling
### 2.1 Error Wrapping with %w (Go 1.13+)
**Core Pattern**: Wrap errors with context using `fmt.Errorf` and `%w`.
```go
func processFile(path string) error {
file, err := os.Open(path)
if err != nil {
// Wrap with context using %w
return fmt.Errorf("failed to open file %s: %w", path, err)
}
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
return fmt.Errorf("failed to read file %s: %w", path, err)
}
return processData(data)
}
// Result when error bubbles up:
// "failed to initialize: failed to open file config.json: open config.json: no such file or directory"
```
**Checking Wrapped Errors**:
```go
// errors.Is - Check for specific error in chain
if errors.Is(err, os.ErrNotExist) {
fmt.Println("File doesn't exist")
}
// errors.As - Extract specific error type
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Printf("Path error on: %s\n", pathErr.Path)
}
```
**Critical**: Use `%w`, NOT `%v`:
```go
// ❌ WRONG: Breaks error chain
return fmt.Errorf("failed: %v", err)
// ✅ CORRECT: Preserves chain
return fmt.Errorf("failed: %w", err)
```
---
### 2.2 Sentinel Errors vs Custom Error Types
**Sentinel Errors** (Package-Level Variables):
```go
package db
var (
ErrConnectionFailed = errors.New("database connection failed")
ErrRecordNotFound = errors.New("record not found")
ErrDuplicateKey = errors.New("duplicate key violation")
)
func GetUser(id int) (*User, error) {
// ...
if notFound {
return nil, ErrRecordNotFound
}
return user, nil
}
// Caller checks with errors.Is
user, err := db.GetUser(123)
if errors.Is(err, db.ErrRecordNotFound) {
// Handle not found
}
```
**Custom Error Types** (Rich Context):
```go
type ValidationError struct {
Field string
Value interface{}
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed for field '%s': %s (value: %v)",
e.Field, e.Message, e.Value)
}
func validateAge(age int) error {
if age < 0 {
return &ValidationError{
Field: "age",
Value: age,
Message: "must be non-negative",
}
}
return nil
}
// Caller extracts rich information
if err := validateAge(-5); err != nil {
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Printf("Field: %s, Value: %v\n", valErr.Field, valErr.Value)
}
}
```
**Decision Guide**:
- **Sentinel errors**: Simple, global error conditions
- **Custom types**: Errors needing structured data or methods
**Important**: Use pointer receivers for error types:
```go
// ✅ CORRECT: Pointer receiver
func (e *ValidationError) Error() string { ... }
// ❌ WRONG: Value receiver (breaks errors.As)
func (e ValidationError) Error() string { ... }
```
---
عرض على GitHub