Pitfalls
Shadowing
Hiding (shadowing) a variable by misusing short declaration.
Such mistakes occur mostly inside the if-body or for-loop
var remember bool = false if something { remember := true // Wrong. } // use remember func shadow() (err error) { x, err := check1() // x is created; err is assigned to if err != nil { return // err correctly returned } if y, err := check2(x); err != nil { // y and inner err are created return // inner err shadows outer err so nil is wrongly returned! } else { fmt.Println(y) } return }
Misusing strings
String concatenations of the kind a += b are inefficient, especially when performed inside a loop.
Instead one should use a bytes.Buffer to accumulate string content
var b bytes.Buffer // ... for condition { b.WriteString(str) // appends string str to the buffer } return b.String()
Using deffer incorrectly
Using defer for closing a file in the wrong scope
It mostly occurs in the for-loop body. Suppose you are processing a range of files in a for-loop, and you want to make sure the files are closed after processing by using defer,
BAD defer sample
for _, file := range files { if f, err = os.Open(file); err != nil { return } defer f.Close() // This is /wrong/. // The file is not closed when this loop iteration ends. // perform operations on f: f.Process(data) }
Defer is only executed at the return of a function, not at the end of a loop or some other limited scope.
DO NOT use defer to close the file in the for-loop body
for _, file := range files { if f, err = os.Open(file); err != nil { return } // perform operations on f: f.Process(data) // close f: f.Close() }
Confusing new() and make()
- for slices, maps and channels, use make
- for arrays, structs, and all value types, use new
No need for slice
No need to pass a pointer to a slice to a function
A slice is a pointer to an underlying array. Passing a slice as a parameter to a function is probably what you always want: namely passing a pointer to a variable to be able to change it, and not passing a copy of the data.
Do not dereference a slice when used as a parameter!
// correct way func findBiggest( listOfNumbers []int ) int {} // wrong way func findBiggest( listOfNumbers *[]int ) int {}
Using pointers to interface
Look at the following program: nexter is an interface with a method next() meaning read the next byte. nextFew1 has this interface type as parameter and reads the next num bytes, returning them as a slice: this is ok.
package main import ( "fmt" ) type nexter interface { next() byte } func nextFew1(n nexter, num int) []byte { var b []byte for i:=0; i < num; i++ { b[i] = n.next() } return b } func nextFew2(n *nexter, num int) []byte { var b []byte for i:=0; i < num; i++ { b[i] = n.next() // compile error: // n.next undefined (type *nexter has no field or method next) } return b } func main() { fmt.Println("Hello World!") }
Misusing pointers
Passing a value as a parameter in a function or as receiver to a method may seem a misuse of memory, because a value is always copied. But on the other hand values are allocated on the stack, which is quick and relatively cheap.
If you would pass a pointer to the value instead the Go compiler in most cases will see this as the making of an object, and will move this object to the heap, so also causing an additional memory allocation: therefore nothing was gained in using a pointer instead of the value!
Misusing goroutines and channel
In practice often you don’t need the concurrency, or you don’t need the overhead of the goroutines with channels, passing parameters using the stack is in many cases far more efficient.
Moreover it is likely to leak memory if you break or return or panic your way out of the loop, because the goroutine then blocks in the middle of doing something. In real code, it is often better to just write a simple procedural loop. Use goroutines and channels only where concurrency is important!
var values = [5]int{10, 11, 12, 13, 14} func main() { // version A: fmt.Println("\nVersion A:") for ix := range values { // ix is the index func() { fmt.Print(ix, " ") }() // call closure, prints each index } fmt.Println() // version B: same as A, but call closure as a goroutine fmt.Println("\nVersion B:") for ix := range values { go func() { fmt.Print(ix, " ") }() } fmt.Println() time.Sleep(5e9) // version C: the right way fmt.Println("\n\nVersion C:") for ix := range values { go func(ix interface{}) { fmt.Print(ix, " ") }(ix) } fmt.Println() time.Sleep(5e9) // version D: print out the values: fmt.Println("\n\nVersion D:") for ix := range values { val := values[ix] go func() { fmt.Print(val, " ") }() } time.Sleep(1e9) } //----- output ------------- // Version A: // 0 1 2 3 4 // Version B: // 4 4 4 4 4 // Version C: // 1 3 4 0 2 // Version D: // 14 10 13 12 11
Bad error handling
Don’t use booleans:
- Making a boolean variable whose value is a test on the error-condition like in the following is superfluous
var good bool
// test for an error, good becomes true or false
if !good {
return errors.New(“things aren’t good”)
}
////--------------
//...
err1 := api.Func1()
if err1 != nil { … }
Don’t clutter your code with error-checking
BAD sample.
// ... err1 := api.Func1() if err1 != nil { fmt.Println(“err: “ + err.Error()) return } err2 := api.Func2() if err2 != nil { //... return }
With the above pattern, it is hard to tell what is normal program logic and what is error checking/reporting. Also notice that most of the code is dedicated to error conditions at any point in the code. A good solution is to wrap your error conditions in a closure wherever possible
err := func () error { if req.Method != “GET” { return errors.New(“expected GET”) } if input := parseInput(req); input != “command” { return errors.New(“malformed command”) } // other error conditions can be tested here } () if err != nil { w.WriteHeader(400) io.WriteString(w, err) return }