57 common mistakes in Golang development

Posted by redarrow on Tue, 22 Feb 2022 06:44:21 +0100

1. A single line of left curly braces is not allowed

2. Unused variables are not allowed

3. Unused import is not allowed (import with package name)

4. Short variable declarations can only be used inside functions

// myvar := 1   // error
var myvar = 1   // ok

5. You cannot duplicate declarations with short variable declarations

6. You cannot use short variable declarations to set field values

data.result, err := work() //error

7. Unexpected variable shadowing
The short variable declaration with the same name in the code block starts from the declaration to the end of the code block. The modification of the variable will not affect the external variable!

8. A variable of unspecified type cannot be initialized with nil

9. Slice and Map with nil value cannot be used directly

10. When a map uses make to allocate memory, you can specify capacity, but you cannot use the cap function on a map

In golang, nil can only be assigned to variables of type pointer, channel, func, interface, map or slice.

12. When an array is used to pass parameters to a function, it is a value copy

Note: when calling a method or function, the passed in parameters are value copy (consistent with assignment), except for map, slice, channel and pointer types, which are passed by reference.

13. The range keyword returns a key value pair, not a value

14. Slice and Array are one-dimensional

15. When a value is taken from a map without a key, the "0 value" is always returned

16. Strings are immutable

17. The conversion between string and [] byte is replication (memory loss). map[string] []byte can be used to establish the mapping between string and [] byte, or range can be used to avoid memory allocation to improve performance

//[]byte: 
for i,v := range []byte(str) {
}

18. The index operation of string returns byte (or uint8). If you want to get characters, you can use for range, unicode/utf8 package and golang At() method of Org / X / exp / utf8string package.

19. The string is not always the text of UTF8

20. len(str) returns the number of bytes of the string

str := "I"
fmt.Println(len(str)) //3

21. Write in multiple lines of Slice, Array and Map, and the last comma cannot be omitted. Write in a single line, and the comma of the last element can be omitted

22. The operations of the built-in data structure are not synchronized, but the concurrent features provided by Go can be used: goroutines and channels.

23. Use for range to iterate String, which is iterated by run.
A character can also be composed of multiple rune s. If you need to process characters, try to use golang Org / X / text / Unicode / norm package.

for range always tries to parse the string into utf8 text. For bytes that it cannot parse, it will return the rune character of oxfffd.
Therefore, any text that contains non utf8 characters must be converted into character slices ([] bytes) first.

24. When using for range to iterate the map, the order of each iteration may be different, because the iteration of the map is random.

25. The default case matching rule of switch is different from that of other languages. After matching the case condition, it exits by default, unless you use fallthrough to continue matching; Other languages continue matching by default, unless you use break to exit matching.

26. There are only post auto increase (a + +) and post auto decrease, and there is no pre auto increase (+ + a) and pre auto decrease

27. The non operation of bit operation is ^ (the same as XOR bit operation), which is different from that of other languages ~.

28. The priority of bit operation (and, or, XOR and negation) is higher than that of four operations (addition, subtraction, multiplication, division and remainder), which is different from C language.

29. Non exported fields (field names starting with lowercase letters) will not be encode d when the structure is serialized, so the values of these non exported fields are "0 value" when decode d

30. The program will exit before all goroutines are finished. The main goroutine can be realized through channel, waiting for all goroutines to complete.

31. For a channel without buffer, the goroutine written to the channel will block until it is read, and the goroutine read from the channel will block until data is written.

32. It is safe to read data from a closed channel. You can judge whether it is closed by the return status (the second return parameter); Writing data to a closed channel will cause panic.

33. Sending or reading data to a channel with a nil value (no space allocated by make) will cause permanent blocking.

34. The method receiver is of type (T). The receiver only copies the value of the original object. Modifying the receiver in the method will not modify the value of the original object; If the method receiver is a pointer type (* t), it is a reference to the original object, and its modification in the method is of course the modification of the original object.

35. Log in the log package Fatal and log Panic not only logs, but also aborts the program. It is different from the Logging library.

36. When using the defer statement to close resources, pay attention to the nil value, and judge the nil value before the defer statement (otherwise, the panic of null reference will be triggered)

37. Close the HTTP connection and use the

  1. req.Close=true, which means to close the connection when the http request is completed
  2. Add the connection request header of Connection: close. The http server will also send the response header of Connection: close, and the http library will close the connection when processing the response.
  3. Turn off http connection reuse globally.

    package main
    
    import (  
     "fmt"
     "net/http"
     "io/ioutil"
    )
    
    func main() {  
     //Global close http connection reuse
     //tr := &http.Transport{DisableKeepAlives: true}
     //client := &http.Client{Transport: tr}
    
     req, err := http.NewRequest("GET","http://golang.org",nil)
     if err != nil {
         fmt.Println(err)
         return
     }
    
     req.Close = true
     //or do this:
     //req.Header.Add("Connection", "close")
    
     resp, err := http.DefaultClient.Do(req)
     if resp != nil {
         defer resp.Body.Close()
     }
    
     if err != nil {
         fmt.Println(err)
         return
     }
    
     body, err := ioutil.ReadAll(resp.Body)
     if err != nil {
         fmt.Println(err)
         return
     }
    
     fmt.Println(len(string(body)))
    }

37. Json deserializes the number into the value of interface {} type, and resolves to float64 type by default

38. Comparison of struct, Array, Slice and Map
If all fields of the struct structure can be compared with = = operation, the structure variables can also be compared with = = operation.
However, if the struct field cannot use = = comparison, the structure variable using = = comparison will lead to compilation errors.

Similarly, array variables can be compared only when each element of array can be compared with = =.

Go provides some functions that cannot be used directly for comparison. The most commonly used function is reflect Deepequal() function.

The slice of the DeepEqual() function for the nil value is not equal to the slice of the empty element, which is different from bytes Equal() function.

var b1 []byte = nil
b2 := []byte{}
fmt.Println("b1 == b2:",reflect.DeepEqual(b1, b2)) //prints: b1 == b2: false

var b3 []byte = nil
b4 := []byte{}
fmt.Println("b3 == b4:",bytes.Equal(b3, b4)) //prints: b3 == b4: true

If you want to ignore case to compare byte slices containing text data,
It is not recommended to use the ToUpper() and ToLower() functions in the bytes package and strings package, and then use = =, byte Equal(),bytes. Compared with compare(), ToUpper() and ToLower() can only process English text, which is invalid for other languages. Therefore, it is recommended to use strings Equalfold() and bytes EqualFold()

If you want to compare byte slices used to verify user data key information, use refresh DeepEqual(),bytes.Equal(),
bytes.Compare() will expose the application to timing attack. You can use crypto / subtle Constanttimecompare() avoids leaking time information.

39. The recover() function can capture or intercept panic, but it must be called directly in the defer function or statement, otherwise it is invalid.

40. The data items obtained in the for range of slice, array and map are copied from the collection elements and do not refer to the original data, but the original data can be accessed by using the index.

data := []int{1,2,3}
for _,v := range data {
    v *= 10          // original item is not changed
}

data2 := []int{1,2,3}
for i,v := range data2 {
    data2[i] *= 10       // change original item
}

// If the element is a pointer type, it is different
data3 := []*struct{num int} {{1}, {2}, {3}}
for _,v := range data {
    v.num *= 10
}

fmt.Println("data:", data)              //prints data: [1 2 3]
fmt.Println("data:", data2)             //prints data: [10 20 30]
fmt.Println(data3[0],data3[1],data3[2])    //prints &{10} &{20} &{30}

41. Generate a slice slice from a slice. The new slice will directly reference the array of the original slice. The operations of the two slices on the same array will affect each other.

You can use copy() to reallocate space for the new slice, and the data in the copy part of the slice can be used to avoid mutual influence.

42. When you continue slicing from an existing slice, the capacity of the new slice is equal to the original capacity minus the number of parts before the new slice. Both the new slice and the original slice point to the same array space.

The capacity area between newly generated slices overlaps, so it is easy to cause the problem of data coverage when adding data.

slice will reallocate space when the content added with append exceeds the capacity.
Taking advantage of this, the slice to be modified is specified with the capacity as the current length of the slice, which can avoid the influence of over coverage between slices.

    path := []byte("AAAA/BBBBBBBBB")
    sepIndex := bytes.IndexByte(path,'/')
    dir1 := path[:sepIndex]
    // resolvent
    // dir1 := path[:sepIndex:sepIndex] //full slice expression
    dir2 := path[sepIndex+1:]
    fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAA
    fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => BBBBBBBBB

    dir1 = append(dir1,"suffix"...)
    path = bytes.Join([][]byte{dir1,dir2},[]byte{'/'})

    fmt.Println("dir1 =>",string(dir1)) //prints: dir1 => AAAAsuffix
    fmt.Println("dir2 =>",string(dir2)) //prints: dir2 => uffixBBBB (not ok)

    fmt.Println("new path =>",string(path))

43. slice shares the same data area with other slices before adding elements, and the modification will affect each other; However, after the memory is reallocated due to the addition of elements, it will no longer point to the original data area, modify the elements, and no longer affect other slices.

    s1 := []int{1,2,3}
    fmt.Println(len(s1),cap(s1),s1) //prints 3 3 [1 2 3]

    s2 := s1[1:]
    fmt.Println(len(s2),cap(s2),s2) //prints 2 2 [2 3]

    for i := range s2 { s2[i] += 20 }

    //still referencing the same array
    fmt.Println(s1) //prints [1 22 23]
    fmt.Println(s2) //prints [22 23]

    s2 = append(s2,4)

    for i := range s2 { s2[i] += 10 }

    //s1 is now "stale"
    fmt.Println(s1) //prints [1 22 23]
    fmt.Println(s2) //prints [32 33 14]

44. Type redefinition and method inheritance

Redefining a new type from an existing non interface type does not inherit any methods of the original type.
You can inherit this anonymous variable type by defining a type that combines anonymous variables.

However, when redefining a new interface from an existing interface, the new interface will inherit all the methods of the original interface.

45. Jump out of the "for switch" and "for select" code blocks.

break without label will only jump out of the innermost switch/select code block.
If you need to jump out of the outer for loop from the switch/select code block, you can define a label outside the for loop for break to jump out.

return is also OK, if it can be used here.

46. There will be problems using iterative variables in the closure of the for statement

During the for iteration, the iteration variable will be retained, but the value of each iteration is different.
Therefore, in the for loop, the iteration variable is directly referenced in the closure, and the value of the iteration variable is directly taken during execution, rather than the variable value of the iteration where the closure is located.

If the closure wants to take the value of the iteration variable, you need to define a variable in for to save the value of the iteration, or pass parameters through the closure function.

47. defer function call parameters

Defer must be followed by a function or method call statement. After defer, whether it is a function or method, the value of the input parameter has been calculated when the defer is declared,
Instead of calling to start the calculation.

It should be noted that when a defer is followed by a method call statement, the receiver of the method is passed when the defer statement is executed, not when the defer statement is declared.

48, defer statement calls are called after the end of the current function, not the scope of the variable.

49. Failed type assertion: when the var.(T) type assertion fails, the "0 value" of type T will be returned instead of the original value of the variable.

func main() {  
      var data interface{} = "great"
      res, ok := data.(int); 
    fmt.Println("res =>",res, ",ok =>",ok)//res => 0 ,ok => false
}

50. Blocked goroutine and resource leakage

func First(query string, replicas ...Search) Result {  
    c := make(chan Result)
    // Solution 1: use buffered channel: C: = make (channel result, len (Replica))
    searchReplica := func(i int) { c <- replicas[i](query) }
    // Solution 2: use Select default to prevent blocking
    // searchReplica := func(i int) { 
    //     select {
    //     case c <- replicas[i](query):
    //     default:
    //     }
    // }
    // Solution 3: use a special channel to interrupt the original work
    // done := make(chan struct{})
    // defer close(done)
    // searchReplica := func(i int) { 
    //     select {
    //     case c <- replicas[i](query):
    //     case <- done:
    //     }
    // }

    for i := range replicas {
        go searchReplica(i)
    }
    return <-c
}

51. Call the method with the receiver as the pointer on the value instance

For addressable value variables (not pointers), you can directly call methods that accept objects as pointer types.
In other words, there is no need to define methods for addressable value variables to accept objects as value types.

However, not all variables are addressable, and elements such as Map are not addressable.

package main

import "fmt"

type data struct {  
    name string
}

func (p *data) print() {  
    fmt.Println("name:",p.name)
}

type printer interface {  
    print()
}

func main() {  
    d1 := data{"one"}
    d1.print() //ok

    // var in printer = data{"two"} //error
    var in printer = &data{"two"}
    in.print()

    m := map[string]data {"x":data{"three"}}
    //m["x"].print() //error
    d2 = m["x"]
    d2.print()      // ok
}

52. Update the field of map value

If the value type of the map is a structure type, the field value of the structure taken from the map cannot be updated.
But slice of structure type is OK.

package main

type data struct {  
    name string
}

func main() {  
    m := map[string]data {"x":{"one"}}
    //m["x"].name = "two" //error
    r := m["x"]
    r.name = "two"
    m["x"] = r
    fmt.Println(s)       // prints: map[x:{two}]

    mp := map[string]*data {"x": {"one"}}
    mp["x"].name = "two" // ok

    s := []data{{"one"}}
    s[0].name = "two"    // ok
    fmt.Println(s)       // prints: [{two}]
}

53. The interface {} of nil value is not equal to nil
In golang, nil can only be assigned to variables of type pointer, channel, func, interface, map or slice.

Interface {} represents any type and can receive any type of value. At the bottom, the interface {} variable is composed of type and value, expressed as (T,V). The interface {} variable is special. When judging that it is nil, its type and value are required to be nil, that is (nil, nil).
For other types of variables, as long as the value is nil, this variable is nil (why? If the variable type is not nil, of course, it can only be judged by the value)

Declare the variable interface {}, which is nil by default, and the underlying type and value are (nil, nil).
When the variable value V of any type T assigns a value to the interface {} variable, the underlying representation of the interface {} variable is (T, V). As long as t is not nil, even if V is nil, the interface {} variable is not nil.

    var data *byte
    var in interface{}

    fmt.Println(data,data == nil) //prints: <nil> true
    fmt.Println(in,in == nil)     //prints: <nil> true

    in = data
    fmt.Println(in,in == nil)     //prints: <nil> false
    //'data' is 'nil', but 'in' is not 'nil'

    doit := func(arg int) interface{} {
        var result *struct{} = nil
        if(arg > 0) {
            result = &struct{}{}
        }
        return result
    }
    if res := doit(-1); res != nil {
        fmt.Println("good result:",res) //prints: good result: <nil>
        //'res' is not 'nil', but its value is 'nil'
    }

    doit = func(arg int) interface{} {
        var result *struct{} = nil
        if(arg > 0) {
            result = &struct{}{}
        } else {
            return nil //return an explicit 'nil'
        }
        return result
    }

    if res := doit(-1); res != nil {
        fmt.Println("good result:",res)
    } else {
        fmt.Println("bad result (res is nil)") //here as expected
    }

54. Allocation of variable memory

Using the new operator in C + + always allocates variables on heap. The Go compiler uses new() and make() to allocate memory. Is it stack or heap,
It depends on the size of the variable and the result of escape analysis. This means that in the Go language, there will be no problem returning references to local variables.

To know the location of variable memory allocation, you can specify - gcflags -m in the go build and go run commands:
go run -gcflags -m app.go

55. GOMAXPROCS, Concurrency and Parallelism

Go 1.4 and below only use one execution context per operating system thread. This means that for each time slice, only one goroutine is executed.
Starting from Go 1.5, you can set the number of execution contexts to the number of CUP cores runtime Numcpu () can also be set through the GOMAXPROCS environment variable,
You can also call runtime Gomaxprocs() function.

Note that GOMAXPROCS does not represent the number of CPUs that can be used by Go runtime. It is a small value of 256. You can set a larger number than the actual number of CPUs.

56. Sorting of read and write operations

Go may sort some operations, but it ensures that all behaviors in goroutine remain unchanged.
However, it cannot guarantee the execution order across multiple goroutine s.

package main

import (  
    "runtime"
    "time"
)

var _ = runtime.GOMAXPROCS(3)

var a, b int

func u1() {  
    a = 1
    b = 2
}

func u2() {  
    a = 3
    b = 4
}

func p() {  
    println(a)
    println(b)
}

func main() {  
    go u1()
    go u2()
    go p()
    time.Sleep(1 * time.Second)
    // Multiple execution can display the following print results
    // 1   2
    // 3   4
    // 0 2 (strange?)
    // 0   0    
    // 1 4 (strange?)
}

57. Priority scheduling

Some rogue goroutines will prevent the execution of other goroutines.
For example, a for loop may not allow the scheduler to execute.

The scheduler will execute immediately after the execution of GC, go statement, blocking channel operation, blocking system call, lock operation and other statements.
It is also possible to execute runtime Gosched() enables the scheduler to perform scheduling work.

package main

import (  
    "fmt"
    "runtime"
)

func main() {  
    done := false
    go func(){
        done = true
    }()

    for !done {
        // ... 
        //runtime.Gosched() / / let the scheduler execute scheduling and give up the execution time slice
    }
    fmt.Println("done!")
}

reference material: https://blog.csdn.net/gezhong...

Topics: Go