← Go EnglishChapter 13 of 13

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 →