Code specification review

Code specification check is a static scan of code according to the Go language specification. This kind of check has nothing to do with business. For example, a constant is defined in a program that has never been used before. Although it has no effect on the code running, it can be removed to save memory. This can be detected by code specification checks.

golangci-lint

Golangci-lint is an integration tool that incorporates a number of static code analysis tools (static code analysis does not run code), and can be configured to enable code specification checking as needed.

The installation

Golangci – lint is written in the language, can from the source code to install it, the terminal input command: Go get github.com/golangci/golangci-lint/cmd/[email protected]

V1.32.2 is installed here. After the installation is complete, check whether the installation is successful. Enter the following command:

golangci-lint version
/ / golangci - lint from version v1.32.2
Copy the code

After the installation, we use it to check the code. For example, we have the following code:

const name = "A little bird's nest."
func main(a){}Copy the code

Golangci -lint run test/ golangci-lint run test/

test\test.go:3:7: `name` is unused (deadcode)
const name = "A little bird's nest."
      ^
test\test.go:4:6: `main` is unused (deadcode)
func main(a){^Copy the code

As you can see, the problem of unused program constants was detected and we were able to refine the code later.

Golangci – lint configuration

The configuration of Golangci-Lint allows you to customize which Linters to enable. Golangci-lint enables linter by default:

Deadcode - deadcode check errcheck - return if error is used check gosimple - check if code can simplify govet - code suspicious check, For example, format strings are inconsistent with types. Ineffassign - Checks for unused code. Staticcheck - Static analysis checks for structcheck - checks for unused structs Unused code check varCheck - Unused global variable and constant checksCopy the code

You can see them clearly by typing the command golangci -Lint linters on your terminal. To change linter, which is enabled by default, create a.golangci.yml file in the root of your project. This will be the configuration file for golangci-Lint. Golangci-lint uses it automatically when running specification checks. Let’s say we use a fixed version of Golangci-Lint for team development, so everyone can check the code against the same criteria. You need to add the following code to the configuration file:

service:
  golangci-lint-version: 1.322. # use the fixed version to not introduce new linters unexpectedly
Copy the code

Golangci-lint. run/usage/confi… . Here is a common configuration for your reference:

linters-settings:
  golint:
    min-confidence: 0
  misspell:
    locale: US
linters:
  disable-all: true
  enable:
    - typecheck
    - goimports
    - misspell
    - govet
    - golint
    - ineffassign
    - gosimple
    - deadcode
    - structcheck
    - unused
    - errcheck
service:
  golangci-lint-version: 1.322. # use the fixed version to not introduce new linters unexpectedly
Copy the code

Integrate Golangci-Lint to CI

Code reviews must be integrated into the CI process so that when the code is submitted, the CI automatically checks the code to find problems and fix them in a timely manner.

Golangci-lint can be run as a Makefile by creating a Makefile in the project root directory with the code:

getdeps:
   @mkdir -p ${GOPATH}/bin
   @which golangci-lint 1>/dev/null || (echo "Installing golangci-lint" && go get github.com/golangci/golangci-lint/cmd/golangci-lint@v132.2.)
lint:
   @echo "Running $@ check"
   @GO111MODULE=on ${GOPATH}/bin/golangci-lint cache clean
   @GO111MODULE=on ${GOPATH}/bin/golangci-lint run --timeout=5m --config ./.golangci.yml
verifiers: getdeps lint
Copy the code

Then you can add the following command to your CI, which will help you automatically install Golangci-Lint and check your code. make verifiers

Heap allocation or stack

The Go language has two parts of memory: stack memory and heap memory.

  • The stack memory is automatically allocated and freed by the compiler, beyond the developer’s control. Stack memory stores local variables and parameters in functions. When functions are created, these memory will be automatically created. This memory is automatically freed when the function returns.

  • Heap memory has a longer lifetime than stack memory, and if the value returned by the function is used elsewhere, it is automatically allocated to the heap by the compiler. Compared to stack memory, heap memory cannot be automatically freed by the compiler and can only be freed by the garbage collector, so stack memory is much more efficient.

Escape analysis

Escape analysis is required to see whether a variable is allocated on the heap or the stack. Example:

func newString(a) *string{
	s := new(string) // New allocates a block of memory to the pointer variable s
	*s = "A little bird's nest."
	return s // Return with the return keyword
}
Copy the code

Escape analysis command:

$ go build -gcflags="-m -l" ./test/test.go
# command-line-arguments
test\test.go:4:8: new(string) escapes to heap
Copy the code
  • -m Displays escape analysis information
  • -l disables inlining to better observe escape

The above results show that escape occurs, indicating that it must occur when a pointer is returned as a function value. Variables that escape into the heap memory cannot be collected immediately, but can only be cleared by garbage collection flag, increasing the pressure of garbage collection, so to avoid escaping as much as possible, let the variables allocated on the stack memory, so that the function can return to reclaim resources, improve efficiency.

Code optimization:

func newString(a) string {
	s := new(string)
	*s = "A little bird's nest."
	return *s
}
Copy the code

Escape analysis command:

$ go build -gcflags="-m -l" ./test/test.go
# command-line-arguments
test\test.go:4:10: new(string) does not escape
Copy the code

Although the pointer variable s is declared, the function does not return a pointer, so it does not escape.

There are three special Go types, slice, map, and Chan, and Pointers referenced by these three types can also escape:

func main(a) {
	m := map[int] *string{}
	s := "A little bird's nest."
	m[0] = &s
}
Copy the code
$ go build -gcflags="-m -l" ./test/test.go
# command-line-arguments
test\test.go:5:2: moved to heap: s
test\test.go:4:22: map[int] *string{} does not escape
Copy the code

The result of escape analysis shows that the variable M does not escape, but the variable S referenced by the variable M escapes to the heap.

  • Pointers referenced by map, slice, and Chan are bound to escape.
  • Although Pointers can reduce memory copy, they can also cause escape, so choose whether to use Pointers according to the actual situation.