Go is a statically typed, compiled programming language designed at Google. It is syntactically similar to C, but with memory safety, garbage collection, structural typing, and CSP-style concurrency.
Go Anti-Patterns Overview
Go, despite its simplicity and strong design principles, still has common anti-patterns that can lead to bugs, performance issues, and maintenance problems. Here are the most important anti-patterns to avoid when writing Go code.
Not Handling Errors Properly
Go’s explicit error handling is a feature, not a burden. Always check and handle errors appropriately, and consider using the %w
verb to wrap errors for better context.
Using Empty Interface (interface{}) Excessively
The empty interface (interface{}
) bypasses Go’s type system. Use specific interfaces that define the behavior you need instead.
Not Using Context for Cancellation
Use context.Context
for cancellation, timeouts, and passing request-scoped values. This allows callers to cancel long-running operations.
Returning Naked Returns
Naked returns (returns without arguments) can make code harder to understand, especially in longer functions. Use explicit returns for clarity.
Using init() Functions Excessively
init()
functions run before main()
and can’t return errors. Use explicit initialization functions that can handle errors gracefully.
Not Using Proper Package Organization
Organize packages by domain, not by technical function. Each package should have a single, well-defined purpose.
Using Global Variables
Global variables make testing difficult and create implicit dependencies. Use dependency injection to make dependencies explicit.
Not Using Interfaces for Testability
Depend on interfaces, not concrete implementations, to make your code more testable and flexible.
Using Pointers Unnecessarily
Only use pointers when you need to modify the data or when the struct is very large. For small, immutable data, use values.
Not Using defer for Cleanup
Use defer
for cleanup operations to ensure they happen even if the function returns early due to an error.
Not Using Structured Logging
Use structured logging with key-value pairs instead of string formatting. This makes logs easier to parse and query.
Not Using Proper Error Types
Define custom error types that implement the error
interface for better error handling and more context.
Not Using Channels Correctly
Always close channels when you’re done sending values. This signals to receivers that no more values will be sent.
Not Using Context Timeout
Use context.WithTimeout
to set timeouts for external calls to prevent your application from hanging indefinitely.
Not Using sync.WaitGroup Correctly
Call wg.Add
before starting goroutines and be careful with loop variables in goroutines.
Not Using Proper Concurrency Patterns
Use established concurrency patterns like worker pools instead of ad-hoc concurrency.
Not Using Proper Error Wrapping
Use fmt.Errorf
with the %w
verb to wrap errors and add context while preserving the original error for checking with errors.Is
and errors.As
.
Not Using Proper Testing Techniques
Use table-driven tests and subtests to make tests more maintainable and to test multiple cases easily.
Not Using go.mod for Dependency Management
Use Go modules (go.mod) for dependency management to ensure reproducible builds and explicit versioning.