- Keep functions under 50 lines (The Screen Rule) - One function = one responsibility - Use early returns instead of nested if statements - Always wrap errors with context using `fmt.Errorf` and `%w` - Use `defer` for guaranteed resource cleanup - Name functions with verb + noun pattern Jump to the checklist for quick reference.- Keep functions under 50 lines (The Screen Rule) - One function = one responsibility - Use early returns instead of nested if statements - Always wrap errors with context using `fmt.Errorf` and `%w` - Use `defer` for guaranteed resource cleanup - Name functions with verb + noun pattern Jump to the checklist for quick reference.

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

2025/10/31 14:30
8 min di lettura
Per feedback o dubbi su questo contenuto, contattateci all'indirizzo crypto.news@mexc.com.

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!

Opportunità di mercato
Logo Particl
Valore Particl (PART)
$0.1516
$0.1516$0.1516
+0.19%
USD
Grafico dei prezzi in tempo reale di Particl (PART)
Disclaimer: gli articoli ripubblicati su questo sito provengono da piattaforme pubbliche e sono forniti esclusivamente a scopo informativo. Non riflettono necessariamente le opinioni di MEXC. Tutti i diritti rimangono agli autori originali. Se ritieni che un contenuto violi i diritti di terze parti, contatta crypto.news@mexc.com per la rimozione. MEXC non fornisce alcuna garanzia in merito all'accuratezza, completezza o tempestività del contenuto e non è responsabile per eventuali azioni intraprese sulla base delle informazioni fornite. Il contenuto non costituisce consulenza finanziaria, legale o professionale di altro tipo, né deve essere considerato una raccomandazione o un'approvazione da parte di MEXC.

Potrebbe anche piacerti

Prediction markets price rising Trump impeachment risks amid Iran conflict. What it signals for 2026 economic uncertainty?

Prediction markets price rising Trump impeachment risks amid Iran conflict. What it signals for 2026 economic uncertainty?

The post Prediction markets price rising Trump impeachment risks amid Iran conflict. What it signals for 2026 economic uncertainty? appeared on BitcoinEthereumNews
Condividi
BitcoinEthereumNews2026/03/15 03:01
Tom Lee’s BitMine Buys $10.2 Million in ETH Directly From Ethereum Foundation

Tom Lee’s BitMine Buys $10.2 Million in ETH Directly From Ethereum Foundation

The post Tom Lee’s BitMine Buys $10.2 Million in ETH Directly From Ethereum Foundation appeared on BitcoinEthereumNews.com. The Ethereum Foundation has made another
Condividi
BitcoinEthereumNews2026/03/15 03:15
Whales Dump 200 Million XRP in Just 2 Weeks – Is XRP’s Price on the Verge of Collapse?

Whales Dump 200 Million XRP in Just 2 Weeks – Is XRP’s Price on the Verge of Collapse?

Whales offload 200 million XRP leaving market uncertainty behind. XRP faces potential collapse as whales drive major price shifts. Is XRP’s future in danger after massive sell-off by whales? XRP’s price has been under intense pressure recently as whales reportedly offloaded a staggering 200 million XRP over the past two weeks. This massive sell-off has raised alarms across the cryptocurrency community, as many wonder if the market is on the brink of collapse or just undergoing a temporary correction. According to crypto analyst Ali (@ali_charts), this surge in whale activity correlates directly with the price fluctuations seen in the past few weeks. XRP experienced a sharp spike in late July and early August, but the price quickly reversed as whales began to sell their holdings in large quantities. The increased volume during this period highlights the intensity of the sell-off, leaving many traders to question the future of XRP’s value. Whales have offloaded around 200 million $XRP in the last two weeks! pic.twitter.com/MiSQPpDwZM — Ali (@ali_charts) September 17, 2025 Also Read: Shiba Inu’s Price Is at a Tipping Point: Will It Break or Crash Soon? Can XRP Recover or Is a Bigger Decline Ahead? As the market absorbs the effects of the whale offload, technical indicators suggest that XRP may be facing a period of consolidation. The Relative Strength Index (RSI), currently sitting at 53.05, signals a neutral market stance, indicating that XRP could move in either direction. This leaves traders uncertain whether the XRP will break above its current resistance levels or continue to fall as more whales sell off their holdings. Source: Tradingview Additionally, the Bollinger Bands, suggest that XRP is nearing the upper limits of its range. This often points to a potential slowdown or pullback in price, further raising concerns about the future direction of the XRP. With the price currently around $3.02, many are questioning whether XRP can regain its footing or if it will continue to decline. The Aftermath of Whale Activity: Is XRP’s Future in Danger? Despite the large sell-off, XRP is not yet showing signs of total collapse. However, the market remains fragile, and the price is likely to remain volatile in the coming days. With whales continuing to influence price movements, many investors are watching closely to see if this trend will reverse or intensify. The coming weeks will be critical for determining whether XRP can stabilize or face further declines. The combination of whale offloading and technical indicators suggest that XRP’s price is at a crossroads. Traders and investors alike are waiting for clear signals to determine if the XRP will bounce back or continue its downward trajectory. Also Read: Metaplanet’s Bold Move: $15M U.S. Subsidiary to Supercharge Bitcoin Strategy The post Whales Dump 200 Million XRP in Just 2 Weeks – Is XRP’s Price on the Verge of Collapse? appeared first on 36Crypto.
Condividi
Coinstats2025/09/17 23:42