langShiftlangShift

Common Pitfalls and Solutions

Go development common pitfalls, JS comparison, solutions and best practices.

Common Pitfalls and Solutions

This module summarizes common traps and misconceptions in Go development, compares them with JavaScript, and helps you avoid common problems to improve code quality.

1. Concurrency Programming Pitfalls

Deadlock

Loading editor...

Solutions

  • Go: Use buffered channels or select to avoid no receiver
  • JS: Use Promise/async properly to avoid callback nesting

Race Conditions

Loading editor...

Solutions

  • Go: Use sync.Mutex or atomic operations
  • JS: Single-threaded avoids most race conditions

2. Memory Leak Issues

Loading editor...

Solutions

  • Go: Close channels timely to avoid goroutine leaks
  • JS: Pay attention to closures and event listener unbinding

3. Performance Optimization Misconceptions

  • Premature optimization leading to complex code
  • Overuse of reflection and interface affecting performance
  • Ignoring memory allocation and GC

Best Practices

  • Ensure correctness first, then optimize performance
  • Use pprof to analyze performance bottlenecks
  • Use types and data structures appropriately

4. Error Handling Pitfalls

Loading editor...

Solutions

  • Go: Handle each error explicitly, avoid ignoring
  • JS: Use try-catch properly, pay attention to async errors

5. Package Management Issues

  • Go: go.mod/go.sum out of sync, dependency conflicts
  • JS: node_modules conflicts, lock file inconsistencies

Best Practices

  • Go: Use go mod tidy to keep dependencies clean
  • JS: Lock dependency versions, clean up regularly

6. Circular Imports

Circular imports happen when package A imports package B, and package B imports package A. Go's compiler strictly forbids this.

Loading editor...

Solutions

  • Interface Decoupling: Define an interface in one package that the other implements, removing the direct dependency.
  • Third Package: Move shared code to a common third package (e.g., common or types) that both A and B import.

7. JSON Serialization Pitfalls (Public/Private Fields)

In Go, only capitalized (exported) fields are serialized to JSON. This is a common stumbling block for JS developers used to everything being public by default.

Loading editor...

Best Practices

  • Always capitalize fields you want to serialize.
  • Use struct tags (e.g., `json:"name"`) to control the output key names (usually lowercase).
  • Lowercase fields are effectively "private" and safe from accidental serialization.

8. Variable Shadowing (The := Trap)

Using := inside a block (like if or for) can create a new local variable that shadows an outer one, leading to confusing bugs.

Loading editor...

Solutions

  • Be careful with :=. If you want to update an existing variable, use = instead.
  • Use go vet or linters to detect shadowing.

9. Slice Append Pitfalls

append in Go returns a new slice descriptor. If you don't assign it back, the changes are lost. Also, multiple slices can share the same underlying array, leading to unexpected side effects.

Loading editor...

10. Defer Execution Order

defer statements are executed in LIFO (Last-In, First-Out) order, like a stack.

package main
import "fmt"
func main() {
defer fmt.Println("First")
defer fmt.Println("Second")
defer fmt.Println("Third")
fmt.Println("Main body")
}
// Output:
// Main body
// Third
// Second
// First

11. Panic in Goroutines

If a goroutine panics and is not recovered, the entire program crashes, not just that goroutine.

Loading editor...

Solutions

  • Use recover() in a defer block inside goroutines to catch panics if you want to prevent crashes.

12. Unused Variables and Imports

Go's compiler is strict: unused variables and imports are compile errors.

  • JavaScript: Linters might warn, but code runs.
  • Go: Code will not compile.
  • Solution: Remove them, or use _ (blank identifier) to ignore values you don't need.

13. Nil Interface vs Nil Concrete Value

An interface is only nil if both its type and value are nil. A nil pointer stored in an interface makes the interface non-nil.

package main
import "fmt"
type MyError struct{}
func (e *MyError) Error() string { return "My error" }
func main() {
var err *MyError = nil
var i interface{} = err
fmt.Println(i == nil) // false! i has type *MyError, value nil
// This causes bugs when checking errors:
// if err != nil { ... } might be true even if the underlying error is nil
}

It's recommended to practice with real projects and consult official documentation and community experience when encountering problems.