Matter AI | Code Reviewer Documentation home pagelight logodark logo
  • Contact
  • Github
  • Sign in
  • Sign in
  • Documentation
  • Blog
  • Discord
  • Github
  • Introduction
    • What is Matter AI?
    Getting Started
    • QuickStart
    Product
    • Security Analysis
    • Code Quality
    • Agentic Chat
    • RuleSets
    • Memories
    • Analytics
    • Command List
    • Configurations
    Patterns
    • Languages
      • Supported Languages
      • Python
      • Java
      • JavaScript
      • TypeScript
      • Node.js
      • React
      • Fastify
      • Next.js
      • Terraform
      • C#
      • C++
      • C
      • Go
      • Rust
      • Swift
      • React Native
      • Spring Boot
      • Kotlin
      • Flutter
      • Ruby
      • PHP
      • Scala
      • Perl
      • R
      • Dart
      • Elixir
      • Erlang
      • Haskell
      • Lua
      • Julia
      • Clojure
      • Groovy
      • Fortran
      • COBOL
      • Pascal
      • Assembly
      • Bash
      • PowerShell
      • SQL
      • PL/SQL
      • T-SQL
      • MATLAB
      • Objective-C
      • VBA
      • ABAP
      • Apex
      • Apache Camel
      • Crystal
      • D
      • Delphi
      • Elm
      • F#
      • Hack
      • Lisp
      • OCaml
      • Prolog
      • Racket
      • Scheme
      • Solidity
      • Verilog
      • VHDL
      • Zig
      • MongoDB
      • ClickHouse
      • MySQL
      • GraphQL
      • Redis
      • Cassandra
      • Elasticsearch
    • Security
    • Performance
    Integrations
    • Code Repositories
    • Team Messengers
    • Ticketing
    Enterprise
    • Enterprise Deployment Overview
    • Enterprise Configurations
    • Observability and Fallbacks
    • Create Your Own GitHub App
    • Self-Hosting Options
    • RBAC
    Patterns
    Languages

    Go

    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, 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.

    // Anti-pattern: Ignoring errors
    func readFile(path string) []byte {
        data, _ := ioutil.ReadFile(path) // Ignoring error
        return data
    }
    
    // Better approach: Handle errors
    func readFile(path string) ([]byte, error) {
        data, err := ioutil.ReadFile(path)
        if err != nil {
            return nil, fmt.Errorf("failed to read file %s: %w", path, err)
        }
        return data, nil
    }

    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.

    // Anti-pattern: Overusing empty interface
    func processData(data interface{}) interface{} {
        // Type assertions everywhere
        switch v := data.(type) {
        case string:
            return v + " processed"
        case int:
            return v * 2
        default:
            return nil
        }
    }
    
    // Better approach: Use specific interfaces
    type Processor interface {
        Process() string
    }
    
    type StringData string
    func (s StringData) Process() string {
        return string(s) + " processed"
    }
    
    type IntData int
    func (i IntData) Process() string {
        return strconv.Itoa(int(i) * 2)
    }
    
    func processData(data Processor) string {
        return data.Process()
    }

    The empty interface (interface{}) bypasses Go’s type system. Use specific interfaces that define the behavior you need instead.

    // Anti-pattern: Not using context for cancellation
    func fetchData(url string) ([]byte, error) {
        resp, err := http.Get(url)
        if err != nil {
            return nil, err
        }
        defer resp.Body.Close()
        return ioutil.ReadAll(resp.Body)
    }
    
    // Better approach: Use context for cancellation
    func fetchData(ctx context.Context, url string) ([]byte, error) {
        req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
        if err != nil {
            return nil, err
        }
        
        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            return nil, err
        }
        defer resp.Body.Close()
        
        return ioutil.ReadAll(resp.Body)
    }

    Use context.Context for cancellation, timeouts, and passing request-scoped values. This allows callers to cancel long-running operations.

    // Anti-pattern: Using naked returns
    func divide(a, b int) (result int, err error) {
        if b == 0 {
            err = errors.New("division by zero")
            return
        }
        result = a / b
        return
    }
    
    // Better approach: Explicit returns
    func divide(a, b int) (int, error) {
        if b == 0 {
            return 0, errors.New("division by zero")
        }
        return a / b, nil
    }

    Naked returns (returns without arguments) can make code harder to understand, especially in longer functions. Use explicit returns for clarity.

    // Anti-pattern: Overusing init()
    var db *sql.DB
    
    func init() {
        var err error
        db, err = sql.Open("postgres", "connection-string")
        if err != nil {
            log.Fatal(err) // Program exits if DB connection fails
        }
    }
    
    // Better approach: Explicit initialization
    func NewApp() (*App, error) {
        db, err := sql.Open("postgres", "connection-string")
        if err != nil {
            return nil, err
        }
        
        return &App{db: db}, nil
    }

    init() functions run before main() and can’t return errors. Use explicit initialization functions that can handle errors gracefully.

    // Anti-pattern: Poor package organization
    // All code in one package
    package main
    
    // User-related code
    type User struct { /* ... */ }
    func CreateUser() { /* ... */ }
    
    // Order-related code
    type Order struct { /* ... */ }
    func PlaceOrder() { /* ... */ }
    
    // Better approach: Organize by domain
    // user/user.go
    package user
    
    type User struct { /* ... */ }
    func Create() { /* ... */ }
    
    // order/order.go
    package order
    
    type Order struct { /* ... */ }
    func Place() { /* ... */ }

    Organize packages by domain, not by technical function. Each package should have a single, well-defined purpose.

    // Anti-pattern: Using global variables
    var (  
        db *sql.DB
        logger *log.Logger
        config Config
    )
    
    func GetUser(id string) (*User, error) {
        // Using global db
        return db.QueryUser(id)
    }
    
    // Better approach: Dependency injection
    type UserService struct {
        db *sql.DB
        logger *log.Logger
        config Config
    }
    
    func NewUserService(db *sql.DB, logger *log.Logger, config Config) *UserService {
        return &UserService{db, logger, config}
    }
    
    func (s *UserService) GetUser(id string) (*User, error) {
        return s.db.QueryUser(id)
    }

    Global variables make testing difficult and create implicit dependencies. Use dependency injection to make dependencies explicit.

    // Anti-pattern: Direct dependency on concrete types
    type UserService struct {
        db *sql.DB
    }
    
    func (s *UserService) GetUser(id string) (*User, error) {
        // Direct dependency on sql.DB
        row := s.db.QueryRow("SELECT * FROM users WHERE id = $1", id)
        // ...
    }
    
    // Better approach: Depend on interfaces
    type Database interface {
        QueryRow(query string, args ...interface{}) Row
    }
    
    type Row interface {
        Scan(dest ...interface{}) error
    }
    
    type UserService struct {
        db Database
    }
    
    func (s *UserService) GetUser(id string) (*User, error) {
        // Dependency on interface, not concrete type
        row := s.db.QueryRow("SELECT * FROM users WHERE id = $1", id)
        // ...
    }

    Depend on interfaces, not concrete implementations, to make your code more testable and flexible.

    // Anti-pattern: Unnecessary pointers
    func NewUser(name string, age int) *User {
        return &User{name, age}
    }
    
    func ProcessUser(user *User) {
        // No modification to user
        fmt.Println(user.name, user.age)
    }
    
    // Better approach: Use values for immutable data
    func NewUser(name string, age int) User {
        return User{name, age}
    }
    
    func ProcessUser(user User) {
        // No modification needed, pass by value
        fmt.Println(user.name, user.age)
    }

    Only use pointers when you need to modify the data or when the struct is very large. For small, immutable data, use values.

    // Anti-pattern: Manual cleanup
    func processFile(path string) error {
        f, err := os.Open(path)
        if err != nil {
            return err
        }
        
        // Process file...
        
        // What if there's an error here? The file won't be closed
        f.Close()
        return nil
    }
    
    // Better approach: Use defer for cleanup
    func processFile(path string) error {
        f, err := os.Open(path)
        if err != nil {
            return err
        }
        defer f.Close() // Will be called even if the function returns early
        
        // Process file...
        
        return nil
    }

    Use defer for cleanup operations to ensure they happen even if the function returns early due to an error.

    // Anti-pattern: Unstructured logging
    func processOrder(order Order) error {
        log.Printf("Processing order %s for customer %s", order.ID, order.CustomerID)
        // Process order...
        log.Printf("Order %s processed successfully", order.ID)
        return nil
    }
    
    // Better approach: Structured logging
    func processOrder(order Order) error {
        logger := log.With(
            "order_id", order.ID,
            "customer_id", order.CustomerID,
        )
        
        logger.Info("Processing order")
        // Process order...
        logger.Info("Order processed successfully")
        return nil
    }

    Use structured logging with key-value pairs instead of string formatting. This makes logs easier to parse and query.

    // Anti-pattern: Using string errors
    func validateUser(user User) error {
        if user.Name == "" {
            return errors.New("name is required")
        }
        if user.Age < 0 {
            return errors.New("age cannot be negative")
        }
        return nil
    }
    
    // Better approach: Define error types
    type ValidationError struct {
        Field string
        Message string
    }
    
    func (e ValidationError) Error() string {
        return fmt.Sprintf("%s: %s", e.Field, e.Message)
    }
    
    func validateUser(user User) error {
        if user.Name == "" {
            return ValidationError{Field: "name", Message: "is required"}
        }
        if user.Age < 0 {
            return ValidationError{Field: "age", Message: "cannot be negative"}
        }
        return nil
    }

    Define custom error types that implement the error interface for better error handling and more context.

    // Anti-pattern: Not closing channels
    func generateNumbers(n int) <-chan int {
        ch := make(chan int)
        go func() {
            for i := 0; i < n; i++ {
                ch <- i
            }
            // Channel is not closed
        }()
        return ch
    }
    
    // Better approach: Close channels when done
    func generateNumbers(n int) <-chan int {
        ch := make(chan int)
        go func() {
            defer close(ch) // Channel is closed when the goroutine exits
            for i := 0; i < n; i++ {
                ch <- i
            }
        }()
        return ch
    }

    Always close channels when you’re done sending values. This signals to receivers that no more values will be sent.

    // Anti-pattern: No timeout for external calls
    func fetchData(ctx context.Context, url string) ([]byte, error) {
        req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
        if err != nil {
            return nil, err
        }
        
        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            return nil, err
        }
        defer resp.Body.Close()
        
        return ioutil.ReadAll(resp.Body)
    }
    
    // Better approach: Use context with timeout
    func fetchData(url string) ([]byte, error) {
        ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
        defer cancel()
        
        req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
        if err != nil {
            return nil, err
        }
        
        resp, err := http.DefaultClient.Do(req)
        if err != nil {
            return nil, err
        }
        defer resp.Body.Close()
        
        return ioutil.ReadAll(resp.Body)
    }

    Use context.WithTimeout to set timeouts for external calls to prevent your application from hanging indefinitely.

    // Anti-pattern: Incorrect WaitGroup usage
    func processItems(items []Item) {
        var wg sync.WaitGroup
        
        for _, item := range items {
            go func() { // Bug: item is captured by reference
                wg.Add(1) // Bug: wg.Add should be called before the goroutine
                defer wg.Done()
                processItem(item)
            }()
        }
        
        wg.Wait()
    }
    
    // Better approach: Correct WaitGroup usage
    func processItems(items []Item) {
        var wg sync.WaitGroup
        wg.Add(len(items)) // Add before starting goroutines
        
        for _, item := range items {
            item := item // Create a new variable for each iteration
            go func() {
                defer wg.Done()
                processItem(item)
            }()
        }
        
        wg.Wait()
    }

    Call wg.Add before starting goroutines and be careful with loop variables in goroutines.

    // Anti-pattern: Ad-hoc concurrency
    func processItems(items []Item) []Result {
        results := make([]Result, len(items))
        var mu sync.Mutex
        
        var wg sync.WaitGroup
        wg.Add(len(items))
        
        for i, item := range items {
            i, item := i, item
            go func() {
                defer wg.Done()
                result := processItem(item)
                
                mu.Lock()
                results[i] = result
                mu.Unlock()
            }()
        }
        
        wg.Wait()
        return results
    }
    
    // Better approach: Worker pool pattern
    func processItems(items []Item) []Result {
        numWorkers := runtime.NumCPU()
        jobs := make(chan Job, len(items))
        results := make(chan Result, len(items))
        
        // Start workers
        var wg sync.WaitGroup
        wg.Add(numWorkers)
        for i := 0; i < numWorkers; i++ {
            go func() {
                defer wg.Done()
                for job := range jobs {
                    results <- processItem(job.Item)
                }
            }()
        }
        
        // Send jobs
        for _, item := range items {
            jobs <- Job{Item: item}
        }
        close(jobs)
        
        // Wait for workers to finish
        go func() {
            wg.Wait()
            close(results)
        }()
        
        // Collect results
        var allResults []Result
        for result := range results {
            allResults = append(allResults, result)
        }
        
        return allResults
    }

    Use established concurrency patterns like worker pools instead of ad-hoc concurrency.

    // Anti-pattern: Losing error context
    func processFile(path string) error {
        data, err := ioutil.ReadFile(path)
        if err != nil {
            return err // Original context is lost
        }
        
        return processData(data)
    }
    
    // Better approach: Wrap errors to add context
    func processFile(path string) error {
        data, err := ioutil.ReadFile(path)
        if err != nil {
            return fmt.Errorf("failed to read file %s: %w", path, err)
        }
        
        if err := processData(data); err != nil {
            return fmt.Errorf("failed to process data from %s: %w", path, err)
        }
        
        return nil
    }

    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.

    // Anti-pattern: Monolithic tests
    func TestUser(t *testing.T) {
        // Test creation, validation, and persistence in one test
        user := NewUser("John", 30)
        if user.Name != "John" || user.Age != 30 {
            t.Errorf("User not created correctly")
        }
        
        err := validateUser(user)
        if err != nil {
            t.Errorf("Valid user failed validation: %v", err)
        }
        
        err = saveUser(user)
        if err != nil {
            t.Errorf("Failed to save user: %v", err)
        }
    }
    
    // Better approach: Table-driven tests with subtests
    func TestNewUser(t *testing.T) {
        tests := []struct {
            name     string
            username string
            age      int
            want     User
        }{
            {"Valid user", "John", 30, User{"John", 30}},
            {"Zero age", "Jane", 0, User{"Jane", 0}},
        }
        
        for _, tt := range tests {
            t.Run(tt.name, func(t *testing.T) {
                got := NewUser(tt.username, tt.age)
                if got != tt.want {
                    t.Errorf("NewUser() = %v, want %v", got, tt.want)
                }
            })
        }
    }

    Use table-driven tests and subtests to make tests more maintainable and to test multiple cases easily.

    // Anti-pattern: Not using go modules
    // Relying on GOPATH or vendor directory
    
    // Better approach: Use go modules
    // go.mod
    module github.com/example/myproject
    
    go 1.16
    
    require (
        github.com/pkg/errors v0.9.1
        github.com/stretchr/testify v1.7.0
    )

    Use Go modules (go.mod) for dependency management to ensure reproducible builds and explicit versioning.

    CRust
    websitexgithublinkedin
    Powered by Mintlify
    Assistant
    Responses are generated using AI and may contain mistakes.