Go Development Tips & Best Practices

1. Avoid Using panic

panic should only be used for truly exceptional situations. Instead, return errors.

Bad Example:

func readFile(filename string) []byte {
    data, err := os.ReadFile(filename)
    if err != nil {
        panic("failed to read file: " + err.Error()) // Improper use of panic
    }
    return data
}

Good Example:

func readFile(filename string) ([]byte, error) {
    data, err := os.ReadFile(filename)
    if err != nil {
        return nil, fmt.Errorf("failed to read file: %w", err)
    }
    return data, nil
}

2. Use Early Returns

Reduce nesting and improve readability by returning early.

Bad Example:

func divide(a, b float64) (float64, error) {
    result := 0.0
    if b != 0 {
        result = a / b
        return result, nil
    } else {
        return 0, errors.New("division by zero")
    }
}

Good Example:

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

3. Always Check Errors

Ignoring errors can make debugging difficult.

Bad Example:

func writeFile(filename string, data []byte) {
    os.WriteFile(filename, data, 0644) // No error handling
}

Good Example:

func writeFile(filename string, data []byte) error {
    err := os.WriteFile(filename, data, 0644)
    if err != nil {
        return fmt.Errorf("failed to write file: %w", err)
    }
    return nil
}

4. Avoid Overusing init

Excessive use of init can lead to hidden side effects.

Bad Example:

var db *sql.DB

func init() {
    var err error
    db, err = sql.Open("mysql", "user:password@/dbname")
    if err != nil {
        panic(err) // Panic inside init
    }
}

Good Example:

func newDB() (*sql.DB, error) {
    db, err := sql.Open("mysql", "user:password@/dbname")
    if err != nil {
        return nil, fmt.Errorf("failed to connect to DB: %w", err)
    }
    return db, nil
}

5. Use log Instead of fmt.Println for Debugging

Use logging instead of direct print statements.

Bad Example:

func process(data string) {
    fmt.Println("Processing:", data) // Debugging print statement
}

Good Example:

func process(data string) {
    log.Printf("Processing: %s", data)
}

6. Avoid Overusing interface{}

Using empty interfaces unnecessarily reduces type safety.

Bad Example:

func printValue(v interface{}) {
    fmt.Println(v) // Accepts any type without restrictions
}

Good Example:

func printValue[T any](v T) {
    fmt.Println(v) // Uses generics
}

7. Use defer Properly

Ensure that defer is used correctly for resource management.

Bad Example:

func processFile() {
    f, _ := os.Open("file.txt")
    defer f.Close() // No error checking
}

Good Example:

func processFile() error {
    f, err := os.Open("file.txt")
    if err != nil {
        return fmt.Errorf("failed to open file: %w", err)
    }
    defer f.Close()
    return nil
}

8. Avoid Capturing Loop Variables in Goroutines

Loop variables should be copied inside the loop to prevent unexpected behavior.

Bad Example:

func process(items []string) {
    for _, item := range items {
        go func() {
            fmt.Println(item) // References shared loop variable
        }()
    }
}

Good Example:

func process(items []string) {
    for _, item := range items {
        itemCopy := item
        go func() {
            fmt.Println(itemCopy) // Uses a copied variable
        }()
    }
}

9. Avoid time.Sleep for Synchronization

Use proper synchronization techniques instead of arbitrary delays.

Bad Example:

func worker() {
    go func() {
        fmt.Println("Working...")
    }()
    time.Sleep(1 * time.Second) // Arbitrary sleep
}

Good Example:

func worker() {
    var wg sync.WaitGroup
    wg.Add(1)
    
    go func() {
        defer wg.Done()
        fmt.Println("Working...")
    }()
    
    wg.Wait() // Proper synchronization
}

10. Avoid Magic Numbers

Define constants for better readability.

Bad Example:

func calculatePrice(quantity int) int {
    return quantity * 100 // 100 is unclear
}

Good Example:

const PricePerUnit = 100

func calculatePrice(quantity int) int {
    return quantity * PricePerUnit
}

Following these best practices will help improve the quality, maintainability, and performance of your Go code. Keep these guidelines in mind when writing Go applications.