In this guide, we will explore the 10 most common mistakes in Go, explain why they happen, and show how to fix them with real-world examples.
Let’s get started! ⚡
1️⃣ Ignoring Errors from Functions
❌ Bad Code (Ignoring Errors)
package main
import "os"
func main() {
f, _ := os.Open("file.txt") // Ignoring error
defer f.Close() // ❌ Will panic if file.txt doesn’t exist
}
🔴 Issue: Ignoring errors leads to silent failures and potential panics.
✅ Good Code (Handling Errors Properly)
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Open("file.txt")
if err != nil {
fmt.Println("Error:", err)
return
}
defer f.Close()
}
✅ Why is this better?
✔ Ensures graceful error handling.
✔ Prevents unexpected crashes.
2️⃣ Not Using defer
Correctly
❌ Bad Code (Using defer
Inside Loops)
package main
import "fmt"
func main() {
for i := 0; i < 3; i++ {
defer fmt.Println(i) // ❌ Executed at function exit, not loop iteration
}
}
🔴 Issue: Deferred calls execute in Last-In-First-Out (LIFO) order when the function exits.
✅ Good Code (Placing defer
Correctly)
package main
import "fmt"
func main() {
for i := 0; i < 3; i++ {
func(n int) {
defer fmt.Println(n) // ✅ Each iteration calls a separate function
}(i)
}
}
✅ Why is this better?
✔ Ensures expected execution order.
3️⃣ Using Global Variables Unnecessarily
❌ Bad Code (Using Global Variables)
package main
import "fmt"
var count int
func increment() {
count++
}
func main() {
increment()
fmt.Println(count) // ❌ Modifying global state (not thread-safe)
}
🔴 Issue: Global variables lead to race conditions in concurrent applications.
✅ Good Code (Using Local Variables or Structs)
package main
import "fmt"
type Counter struct {
count int
}
func (c *Counter) increment() {
c.count++
}
func main() {
c := Counter{}
c.increment()
fmt.Println(c.count) // ✅ Local scope ensures safety
}
✅ Why is this better?
✔ Avoids race conditions in concurrent programs.
4️⃣ Not Closing Resources
❌ Bad Code (Forgetting to Close Files)
package main
import "os"
func main() {
f, err := os.Open("file.txt")
if err != nil {
return
}
// ❌ Forgot to close file, causing a resource leak
}
🔴 Issue: Leads to memory leaks and open file descriptor issues.
✅ Good Code (Using defer f.Close()
)
package main
import (
"fmt"
"os"
)
func main() {
f, err := os.Open("file.txt")
if err != nil {
fmt.Println("Error:", err)
return
}
defer f.Close() // ✅ Ensures file is closed at function exit
}
✅ Why is this better?
✔ Prevents resource leaks.
5️⃣ Using Slices Incorrectly
❌ Bad Code (Modifying the Underlying Array)
package main
import "fmt"
func main() {
arr := []int{1, 2, 3, 4}
slice := arr[:2]
slice = append(slice, 5, 6) // ❌ Modifies original array
fmt.Println(arr) // Unexpected result
}
🔴 Issue: Append can modify the original array unexpectedly.
✅ Good Code (Using copy()
)
package main
import "fmt"
func main() {
arr := []int{1, 2, 3, 4}
slice := make([]int, len(arr[:2]))
copy(slice, arr[:2])
slice = append(slice, 5, 6) // ✅ Does not modify original array
fmt.Println(arr) // Unchanged
}
✅ Why is this better?
✔ Avoids unexpected modifications.
6️⃣ Using Unbuffered Channels in High-Concurrency Applications
❌ Bad Code (Blocking a Goroutine)
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
ch <- 42 // ❌ Deadlock if no receiver
}()
fmt.Println(<-ch)
}
🔴 Issue: If no receiver is available, the program deadlocks.
✅ Good Code (Using Buffered Channels)
package main
import "fmt"
func main() {
ch := make(chan int, 1)
ch <- 42 // ✅ Non-blocking send
fmt.Println(<-ch)
}
✅ Why is this better?
✔ Avoids deadlocks in concurrent applications.
7️⃣ Improper Error Wrapping
❌ Bad Code (Losing Context in Errors)
package main
import (
"errors"
"fmt"
)
func readFile() error {
return errors.New("file not found")
}
func main() {
err := readFile()
fmt.Println(err) // ❌ No context on where the error happened
}
🔴 Issue: The error message doesn’t indicate where it came from.
✅ Good Code (Using fmt.Errorf
)
package main
import (
"fmt"
)
func readFile() error {
return fmt.Errorf("readFile error: %w", fmt.Errorf("file not found"))
}
func main() {
err := readFile()
fmt.Println(err) // ✅ Shows where the error originated
}
✅ Why is this better?
✔ Provides clearer error messages.
8️⃣ Misusing Goroutines
❌ Bad Code (Launching Too Many Goroutines)
package main
import "fmt"
func main() {
for i := 0; i < 1000000; i++ {
go fmt.Println(i) // ❌ Can cause memory exhaustion
}
}
🔴 Issue: Spawning too many goroutines exceeds system limits.
✅ Good Code (Using Worker Pools)
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
jobs := make(chan int, 10)
for i := 0; i < 5; i++ { // ✅ Limited workers
wg.Add(1)
go func() {
for job := range jobs {
fmt.Println(job)
}
wg.Done()
}()
}
for j := 0; j < 10; j++ {
jobs <- j
}
close(jobs)
wg.Wait()
}
✅ Why is this better?
✔ Controls resource usage efficiently.
9️⃣ Not Using context.Context
❌ Bad Code (No Timeout Control)
package main
import (
"net/http"
)
func fetchData() {
_, _ = http.Get("https://example.com") // ❌ No timeout
}
🔴 Issue: No timeout control means requests can hang indefinitely.
✅ Good Code (Using context.WithTimeout
)
package main
import (
"context"
"net/http"
"time"
)
func fetchData() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", "https://example.com", nil)
_, _ = http.DefaultClient.Do(req)
}
✅ Why is this better?
✔ Prevents stuck requests.
🔟 Using panic
for Error Handling
❌ Bad Code (Using panic
)
package main
func divide(a, b int) int {
if b == 0 {
panic("cannot divide by zero") // ❌ Terminates program
}
return a / b
}
✅ Good Code (Returning an Error)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide by zero")
}
return a / b, nil
}
✅ Why is this better?
✔ Avoids unexpected crashes.
🎯 Conclusion
By avoiding these 10 common Go mistakes, you can write faster, more reliable, and more maintainable Go applications. 🚀
Comments
Post a Comment
Leave Comment