By using this site, you agree to the Privacy Policy and Terms of Use.
Accept
World of SoftwareWorld of SoftwareWorld of Software
  • News
  • Software
  • Mobile
  • Computing
  • Gaming
  • Videos
  • More
    • Gadget
    • Web Stories
    • Trending
    • Press Release
Search
  • Privacy
  • Terms
  • Advertise
  • Contact
Copyright © All Rights Reserved. World of Software.
Reading: Clean Code: Functions and Error Handling in Go: From Chaos to Clarity [Part 1] | HackerNoon
Share
Sign In
Notification Show More
Font ResizerAa
World of SoftwareWorld of Software
Font ResizerAa
  • Software
  • Mobile
  • Computing
  • Gadget
  • Gaming
  • Videos
Search
  • News
  • Software
  • Mobile
  • Computing
  • Gaming
  • Videos
  • More
    • Gadget
    • Web Stories
    • Trending
    • Press Release
Have an existing account? Sign In
Follow US
  • Privacy
  • Terms
  • Advertise
  • Contact
Copyright © All Rights Reserved. World of Software.
World of Software > Computing > Clean Code: Functions and Error Handling in Go: From Chaos to Clarity [Part 1] | HackerNoon
Computing

Clean Code: Functions and Error Handling in Go: From Chaos to Clarity [Part 1] | HackerNoon

News Room
Last updated: 2025/10/31 at 8:41 AM
News Room Published 31 October 2025
Share
Clean Code: Functions and Error Handling in Go: From Chaos to Clarity [Part 1] | HackerNoon
SHARE

Introduction: Why Go Functions Are Special

I’ve reviewed over 1000 pull requests in Go over the past 6 years, and the same mistakes keep appearing. Remember your first Go code? It probably had dozens of if err != nil checks and 200-line functions that did everything at once. After analyzing over 50 Go projects, I’ve identified the main beginner problem: they write Go like Java or Python, ignoring the language’s idioms.

Common function problems I’ve seen:

  • Functions over 100 lines: ~40% of codebases
  • Mixed responsibilities: ~60% of functions
  • Poor error handling: ~30% of bugs
  • Missing defer for cleanup: ~45% of resource leaks

In this article — the first in a Clean Code in Go series — we’ll explore how to write functions you won’t be ashamed to show in code review. We’ll discuss the single responsibility principle, error handling, and why defer is your best friend.

Single Responsibility Principle: One Function — One Job

Here’s a typical function from a real project (names changed):

// BAD: monster function does everything
func ProcessUserData(userID int) (*User, error) {
    // Validation
    if userID <= 0 {
        log.Printf("Invalid user ID: %d", userID)
        return nil, errors.New("invalid user ID")
    }

    // Database connection
    db, err := sql.Open("postgres", connString)
    if err != nil {
        log.Printf("DB connection failed: %v", err)
        return nil, err
    }
    defer db.Close()

    var user User
    err = db.QueryRow("SELECT * FROM users WHERE id = $1", userID).Scan(&user.ID, &user.Name, &user.Email)
    if err != nil {
        log.Printf("Query failed: %v", err)
        return nil, err
    }

    // Data enrichment
    if user.Email != "" {
        domain := strings.Split(user.Email, "@")[1]
        user.EmailDomain = domain

        // Check corporate domain
        corporateDomains := []string{"google.com", "microsoft.com", "apple.com"}
        for _, corp := range corporateDomains {
            if domain == corp {
                user.IsCorporate = true
                break
            }
        }
    }

    // Logging
    log.Printf("User %d processed successfully", userID)

    return &user, nil
}

This function violates SRP on multiple fronts:

  • Validates input data
  • Manages database connections
  • Executes queries
  • Enriches data
  • Handles logging

The Screen Rule

Quality metric: A function should fit entirely on a developer’s screen (roughly 30-50 lines). If you need to scroll — time to refactor.

Let’s refactor following Go idioms:

// GOOD: each function has one responsibility
func GetUser(ctx context.Context, userID int) (*User, error) {
    if err := validateUserID(userID); err != nil {
        return nil, fmt.Errorf("validation failed: %w", err)
    }

    user, err := fetchUserFromDB(ctx, userID)
    if err != nil {
        return nil, fmt.Errorf("fetch user %d: %w", userID, err)
    }

    enrichUserData(user)
    return user, nil
}

func validateUserID(id int) error {
    if id <= 0 {
        return fmt.Errorf("invalid user ID: %d", id)
    }
    return nil
}

func fetchUserFromDB(ctx context.Context, userID int) (*User, error) {
    row := db.QueryRowContext(ctx, `
        SELECT id, name, email 
        FROM users 
        WHERE id = $1`, userID)

    var user User
    if err := row.Scan(&user.ID, &user.Name, &user.Email); err != nil {
        if errors.Is(err, sql.ErrNoRows) {
            return nil, ErrUserNotFound
        }
        return nil, err
    }

    return &user, nil
}

func enrichUserData(user *User) {
    if user.Email == "" {
        return
    }

    parts := strings.Split(user.Email, "@")
    if len(parts) != 2 {
        return
    }

    user.EmailDomain = parts[1]
    user.IsCorporate = isCorporateDomain(user.EmailDomain)
}

Now each function:

  • Fits on screen (20 lines max)
  • Has single responsibility
  • Can be tested independently

Error Handling: The Go Way

Problem: Nested Hell

Beginners often create the “pyramid of doom”:

// BAD: deep nesting
func SendNotification(userID int, message string) error {
    user, err := GetUser(userID)
    if err == nil {
        if user.Email != "" {
            if user.IsActive {
                if user.NotificationsEnabled {
                    err := smtp.Send(user.Email, message)
                    if err == nil {
                        log.Printf("Sent to %s", user.Email)
                        return nil
                    } else {
                        log.Printf("Failed to send: %v", err)
                        return err
                    }
                } else {
                    return errors.New("notifications disabled")
                }
            } else {
                return errors.New("user inactive")
            }
        } else {
            return errors.New("email empty")
        }
    } else {
        return fmt.Errorf("user not found: %v", err)
    }
}

Solution: Early Return (Guard Clauses)

// GOOD: early return on errors
func SendNotification(userID int, message string) error {
    user, err := GetUser(userID)
    if err != nil {
        return fmt.Errorf("get user %d: %w", userID, err)
    }

    if user.Email == "" {
        return ErrEmptyEmail
    }

    if !user.IsActive {
        return ErrUserInactive
    }

    if !user.NotificationsEnabled {
        return ErrNotificationsDisabled
    }

    if err := smtp.Send(user.Email, message); err != nil {
        return fmt.Errorf("send to %s: %w", user.Email, err)
    }

    log.Printf("Notification sent to %s", user.Email)
    return nil
}

Error Wrapping: Context Matters

Since Go 1.13, fmt.Errorf with the %w verb wraps errors. Always use it:

// Define sentinel errors for business logic
var (
    ErrUserNotFound          = errors.New("user not found")
    ErrInsufficientFunds     = errors.New("insufficient funds")
    ErrOrderAlreadyProcessed = errors.New("order already processed")
)

func ProcessPayment(orderID string) error {
    order, err := fetchOrder(orderID)
    if err != nil {
        // Add context to the error
        return fmt.Errorf("process payment for order %s: %w", orderID, err)
    }

    if order.Status == "processed" {
        return ErrOrderAlreadyProcessed
    }

    if err := chargeCard(order); err != nil {
        // Wrap technical errors
        return fmt.Errorf("charge card for order %s: %w", orderID, err)
    }

    return nil
}

// Calling code can check error type
if err := ProcessPayment("ORD-123"); err != nil {
    if errors.Is(err, ErrOrderAlreadyProcessed) {
        // Business logic for already processed order
        return nil
    }

    if errors.Is(err, ErrInsufficientFunds) {
        // Notify user about insufficient funds
        notifyUser(err)
    }

    // Log unexpected errors
    log.Printf("Payment failed: %v", err)
    return err
}

Defer: Guaranteed Resource Cleanup

defer is one of Go’s killer features. Use it for guaranteed cleanup:

// BAD: might forget to release resources
func ReadConfig(path string) (*Config, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, err
    }

    data, err := io.ReadAll(file)
    if err != nil {
        file.Close() // Easy to forget during refactoring
        return nil, err
    }

    var config Config
    if err := json.Unmarshal(data, &config); err != nil {
        file.Close() // Duplication
        return nil, err
    }

    file.Close() // And again
    return &config, nil
}
// GOOD: defer guarantees closure
func ReadConfig(path string) (*Config, error) {
    file, err := os.Open(path)
    if err != nil {
        return nil, fmt.Errorf("open config %s: %w", path, err)
    }
    defer file.Close() // Will execute no matter what

    data, err := io.ReadAll(file)
    if err != nil {
        return nil, fmt.Errorf("read config %s: %w", path, err)
    }

    var config Config
    if err := json.Unmarshal(data, &config); err != nil {
        return nil, fmt.Errorf("parse config %s: %w", path, err)
    }

    return &config, nil
}

Pattern: Cleanup Functions

func WithTransaction(ctx context.Context, fn func(*sql.Tx) error) error {
    tx, err := db.BeginTx(ctx, nil)
    if err != nil {
        return fmt.Errorf("begin transaction: %w", err)
    }

    // defer executes in LIFO order
    defer func() {
        if p := recover(); p != nil {
            tx.Rollback()
            panic(p) // re-throw panic after cleanup
        }

        if err != nil {
            tx.Rollback()
        } else {
            err = tx.Commit()
        }
    }()

    err = fn(tx)
    return err
}

// Usage
err := WithTransaction(ctx, func(tx *sql.Tx) error {
    // All logic in transaction
    // Rollback/Commit happens automatically
    return nil
})

Practical Tips

1. Function Naming

// BAD: unclear purpose
func Process(data []byte) error
func Handle(r Request) Response
func Do() error

// GOOD: verb + noun
func ParseJSON(data []byte) (*Config, error)
func ValidateEmail(email string) error
func SendNotification(user *User, msg string) error

2. Function Parameters

If more than 3-4 parameters — use a struct:

// BAD: too many parameters
func CreateUser(name, email, phone, address string, age int, isActive bool) (*User, error)

// GOOD: group into struct
type CreateUserRequest struct {
    Name     string
    Email    string
    Phone    string
    Address  string
    Age      int
    IsActive bool
}

func CreateUser(req CreateUserRequest) (*User, error)

3. Return Values

// BAD: boolean flags are unclear
func CheckPermission(userID int) (bool, bool, error) // what does first bool mean? second?

// GOOD: use named returns or struct
func CheckPermission(userID int) (canRead, canWrite bool, err error)

// BETTER: struct for complex results
type Permissions struct {
    CanRead   bool
    CanWrite  bool
    CanDelete bool
}

func CheckPermission(userID int) (*Permissions, error)

Clean Function Checklist

  • Fits on screen (30-50 lines max)
  • Does one thing (Single Responsibility)
  • Has clear name (verb + noun)
  • Uses early return for errors
  • Wraps errors with context (%w)
  • Uses defer for cleanup
  • Accepts context if can be cancelled
  • No side effects (or clearly documented)

Conclusion

Clean functions in Go aren’t just about following general Clean Code principles. It’s about understanding and using language idioms: early return instead of nesting, error wrapping for context, defer for guaranteed cleanup.

In the next article, we’ll discuss structs and methods: when to use value vs pointer receivers, how to organize composition properly, and why embedding isn’t inheritance.

What’s your approach to keeping functions clean? Do you have a maximum line limit for your team? Let me know in the comments!

Sign Up For Daily Newsletter

Be keep up! Get the latest breaking news delivered straight to your inbox.
By signing up, you agree to our Terms of Use and acknowledge the data practices in our Privacy Policy. You may unsubscribe at any time.
Share This Article
Facebook Twitter Email Print
Share
What do you think?
Love0
Sad0
Happy0
Sleepy0
Angry0
Dead0
Wink0
Previous Article Inside the secret psychology of horror games – and why we can’t help pushing play Inside the secret psychology of horror games – and why we can’t help pushing play
Next Article Best robot vacuum deal: Save 0 on Roborock Qrevo Edge Best robot vacuum deal: Save $350 on Roborock Qrevo Edge
Leave a comment

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Stay Connected

248.1k Like
69.1k Follow
134k Pin
54.3k Follow

Latest News

13 Horror Movies on Prime Video You Should Dare to Watch on Halloween
13 Horror Movies on Prime Video You Should Dare to Watch on Halloween
News
Inside MEM3 Tokenomics: A Balanced Path to Community Growth
Inside MEM3 Tokenomics: A Balanced Path to Community Growth
Gadget
Apple’s festive streaming plans have us very excited for Christmas
Apple’s festive streaming plans have us very excited for Christmas
Gadget
Here’s Why We’re Still Recommending TP-Link Routers, Despite Security Concerns
Here’s Why We’re Still Recommending TP-Link Routers, Despite Security Concerns
News

You Might also Like

OpenAI Unveils Aardvark: GPT-5 Agent That Finds and Fixes Code Flaws Automatically
Computing

OpenAI Unveils Aardvark: GPT-5 Agent That Finds and Fixes Code Flaws Automatically

4 Min Read
‘Big Beautiful’ tax benefit: Amazon and other tech giants reap the rewards of new law, for now
Computing

‘Big Beautiful’ tax benefit: Amazon and other tech giants reap the rewards of new law, for now

6 Min Read
Bass’s Proposal to Exempt Palisades Properties From Measure ULA Draws Scrutiny – Knock LA
Computing

Bass’s Proposal to Exempt Palisades Properties From Measure ULA Draws Scrutiny – Knock LA

6 Min Read
The 9 Best Affiliate Recruitment Tools to Scale Your Affiliate Program | HackerNoon
Computing

The 9 Best Affiliate Recruitment Tools to Scale Your Affiliate Program | HackerNoon

29 Min Read
//

World of Software is your one-stop website for the latest tech news and updates, follow us now to get the news that matters to you.

Quick Link

  • Privacy Policy
  • Terms of use
  • Advertise
  • Contact

Topics

  • Computing
  • Software
  • Press Release
  • Trending

Sign Up for Our Newsletter

Subscribe to our newsletter to get our newest articles instantly!

World of SoftwareWorld of Software
Follow US
Copyright © All Rights Reserved. World of Software.
Welcome Back!

Sign in to your account

Lost your password?