Best Practices
## Learning Objectives
- Follow Go formatting conventions
- Write clear and idiomatic code
- Master effective error handling
- Write and run tests
- Document code properly
## Formatting (gofmt)
### Automatic Formatting
```bash
gofmt -w main.go # Format and write back
gofmt -d main.go # Show diff
gofmt -e main.go # Show syntax errors
```
### go fmt
```bash
go fmt ./... # Run gofmt on all files
```
### IDE Integration
Most IDEs format on save automatically.
## Style Guide
### Line Length
Go doesn't enforce line length. Write for readability.
### Indentation
Use tabs. Let your editor handle conversion.
### Braces
```go
// Correct
if x > 0 {
fmt.Println("positive")
}
// Wrong
if x > 0
{
fmt.Println("positive")
}
```
### Grouping Imports
```go
import (
"fmt"
"math"
"strings"
"github.com/pkg/errors"
)
```
### Grouping Variables
```go
// Good: grouped declarations
var (
count int
name string
ready bool
)
```
## Naming Conventions
### Variables
- Short but meaningful
- camelCase for local variables
- PascalCase for exported
```go
var name string // Local
var UserName string // Exported
i, j := 0, 1 // Loop variables
ctx := context.Background() // Context
```
### Constants
- PascalCase for exported
- camelCase for unexported
```go
const MaxRetries = 3 // Exported
const maxCacheSize = 100 // Unexported
```
### Functions
- PascalCase for exported
- camelCase for unexported
```go
func CalculateArea(r float64) float64 {} // Exported
func calculateInterest() {} // Unexported
```
### Packages
- Short, lowercase, no underscores
- Descriptive but concise
```go
import "strings"
import "context"
import "errgroup"
```
### Interface Names
- Add `-er` suffix when appropriate
- Describe the behavior
```go
type Reader interface {}
type Writer interface {}
type ReadWriter interface {}
```
## Error Handling
### Explicit Error Handling
```go
// Good
if err != nil {
return fmt.Errorf("operation failed: %w", err)
}
// Bad: ignoring errors
result, _ := riskyCall()
```
### Wrap Errors with Context
```go
// Good
if err := readConfig(path); err != nil {
return fmt.Errorf("loading config from %s: %w", path, err)
}
// Bad
if err := readConfig(path); err != nil {
return err
}
```
### Sentinel Errors
```go
var ErrNotFound = errors.New("not found")
func find(id string) error {
if id == "missing" {
return ErrNotFound
}
return nil
}
```
## Comments
### Documentation Comments
```go
// Package utils provides utility functions for string manipulation.
package utils
// Add returns the sum of two integers.
func Add(a, b int) int {
return a + b
}
```
### Inline Comments
```go
// Ensure capacity for new elements
if len(slice) >= cap(slice) {
slice = append(slice, make(T, 10)...)
}
```
### Comment Patterns
```go
// TODO: Add validation
// FIXME: Handle edge case
// NOTE: This is intentional because...
```
## Code Structure
### Keep Functions Small
```go
// Good: focused function
func validateEmail(email string) error {
if !strings.Contains(email, "@") {
return ErrInvalidEmail
}
return nil
}
// Good: composed in larger function
func registerUser(email string) error {
if err := validateEmail(email); err != nil {
return err
}
// Continue registration...
}
```
### Early Returns
```go
// Good
func process(data []byte) error {
if len(data) == 0 {
return ErrEmptyData
}
// Process data...
}
// Bad
func process(data []byte) error {
if len(data) > 0 {
// Process data...
} else {
return ErrEmptyData
}
}
```
### Avoid Nested Conditionals
```go
// Good: early return
func findUser(id string) (*User, error) {
if id == "" {
return nil, ErrInvalidID
}
user, err := db.GetUser(id)
if err != nil {
return nil, err
}
return user, nil
}
```
## Testing
### Test File Naming
```text
add.go -> add_test.go
utils/helpers.go -> utils/helpers_test.go
```
### Table-Driven Tests
```go
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
want int
}{
{"positive", 2, 3, 5},
{"negative", -1, -1, -2},
{"zero", 0, 5, 5},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.want {
t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
}
})
}
}
```
### Subtests
```go
func TestMath(t *testing.T) {
t.Run("Add", func(t *testing.T) {
if Add(2, 3) != 5 {
t.Error("Add failed")
}
})
t.Run("Multiply", func(t *testing.T) {
if Multiply(2, 3) != 6 {
t.Error("Multiply failed")
}
})
}
```
### Testing Errors
```go
func TestDivide(t *testing.T) {
_, err := Divide(10, 0)
if err == nil {
t.Error("Expected error for division by zero")
}
if !errors.Is(err, ErrDivisionByZero) {
t.Errorf("Expected ErrDivisionByZero, got %v", err)
}
}
```
### Benchmarking
```go
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(1, 2)
}
}
```
```bash
go test -bench=. -benchmem
```
### Running Tests
```bash
go test # Run tests
go test -v # Verbose
go test -run Pattern # Run matching tests
go test -cover # Show coverage
go test -race # Check for races
go test ./... # All packages
```
## Concurrency Best Practices
### Don't Leak Goroutines
```go
// Good: controlled goroutine
done := make(chan struct{})
go func() {
// Work...
close(done)
}()
<-done
```
### Context for Cancellation
```go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
go doWork(ctx)
```
### Avoid Shared State
```go
// Good: communicate shared data
ch := make(chan int)
go func() { ch <- result }()
value := <-ch
```
## Performance Tips
### Preallocate Slices
```go
// Good
slice := make([]int, 0, 100)
for i := 0; i < 100; i++ {
slice = append(slice, i)
}
// Bad (multiple reallocations)
var slice []int
for i := 0; i < 100; i++ {
slice = append(slice, i)
}
```
### Use strings.Builder
```go
var sb strings.Builder
for i := 0; i < 1000; i++ {
sb.WriteString("hello")
}
result := sb.String()
```
### Avoid []byte to string Conversions
```go
// In hot paths
data := []byte("hello")
s := string(data) // Allocation!
// Better: keep as []byte when possible
data := []byte("hello")
// Use data directly
```
## Documentation with godoc
### Running godoc
```bash
go doc fmt.Println
godoc -http=:8080
```
### Export for Documentation
All exported identifiers are documented in godoc.
## Summary
- Use `gofmt` or `go fmt` for formatting
- Follow naming conventions: camelCase local, PascalCase exported
- Handle errors explicitly; don't ignore them
- Write comments for exported functions and packages
- Write table-driven tests for comprehensive coverage
- Keep functions small and focused
- Use early returns to reduce nesting
- Don't leak goroutines; use context for cancellation
- Preallocate slices to avoid reallocations
- Document exported code for godoc
Comments
Comments powered by Giscus
To enable comments, add your Giscus embed code here.
Learn more about Giscus →