Golang

This section contains development guidelines about writing code in Go.

Coding guidelines

General style

A1. Go projects should follow the Standard Go Project Layout. To this end you may use the Go Project Template.

A2. Code should be formatted according to the rules of go fmt and go vet. Example or a working Make target used in several projects, that allows to format a Go module:

test_fmt:
	@echo Checking correct formatting of files
	@{ \
		files=$$( go fmt ./... ); \
		if [ -n "$$files" ]; then \
		echo "Files not properly formatted: $$files"; \
		exit 1; \
		fi; \
		if ! go vet ./...; then \
		exit 1; \
		fi \
	}

A3. Code should adhere to the golint rules. Example of a working Make target used in several projects, that allows to check a Go module linting:

test_lint:
	@echo Checking linting of files
	@{ \
		GO111MODULE=off go get -u golang.org/x/lint/golint; \
		el=$(EXCLUDE_LINT); \
		lintfiles=$$( golint ./... | egrep -v "$$el" ); \
		if [ -n "$$lintfiles" ]; then \
		echo "Lint errors:"; \
		echo "$$lintfiles"; \
		exit 1; \
		fi \
	}

A4. To set up local variables in if and switch control structures, you should use whenever applicable the initialisation statement construct.

if err := file.Chmod(0664); err != nil {
    log.Print(err)
    return err
}

A5. To loop over an array, slice, string or map, or reading from a channel, you should use the range clause.

for key, value := range oldMap {
    newMap[key] = value
}

A6. To generate cryptographic keys, you must not use math/rand. Use crypto/rand's Reader instead, and print to hexadecimal or base64 if you need text. crypto/rand should always be used, even in tests or in contexts that does not have high security requirements, to avoid the introduction of vulnerabilities in later development stages stemming from incautious copy-paste operations.

import (
	"crypto/rand"
	// "encoding/base64"
	// "encoding/hex"
	"fmt"
)

func Key() string {
	buf := make([]byte, 16)
	_, err := rand.Read(buf)
	if err != nil {
		panic(err)  // out of randomness, should never happen
	}
	return fmt.Sprintf("%x", buf)
	// or hex.EncodeToString(buf)
	// or base64.StdEncoding.EncodeToString(buf)
}

A7. When declaring an empty slice, prefer this notation:

var t []string

Variables, functions, packages and modules

B1. MixedCaps should be used to write multiword names.

B2. Words in names that are initialisms or acronyms (e.g. "URL" or "NATO") should have a consistent case. For example, "URL" should appear as "URL" or "url" (as in "urlPony", or "URLPony"), never as "Url". This rule also applies to "ID" when it is short for "identifier", so you should write "appID" instead of "appId".

B3. Named result parameters should be used only in short functions.

B4. The defer statement should be used to release resources used by functions (e.g. I/O operations).

B5. Security credentials, tracing information, deadlines, and cancellation signals should be passed across functions as values of the context.Context type.

B6. Functions using a Context should accept it as first parameter.

B7. A function that is never request-specific may use context.Background() instead of Context.

B8. Functions should always return an error or boolean to indicate whether its other return values are valid or not, and not rely on special or magic return values to indicate error.

B9. Packages should have single-word names, and they must match the base name of their source directory.

As an example, the package insrc/encoding/base64 should have name base64(and be imported asencoding/base64).

B10. Modules should be defined by using go.mod.

Errors and log

C1. Functions should always return an error or boolean to indicate wether its other return values are valid or not, and not rely on special or magic return values to indicate error. For severe errors which cause the application to crash, the panic function may be used instead.

C2. The panic function should be used with caution and not for normal error handling. If used, it should be properly documented so that it has the chance to be caught.

C3. The log.Fatal function should not be used.

C4. Error and log strings should not be capitalised nor end with punctuation.

Testing

D1. Testing of Go projects should be implemented by using the built-in testing package provided by the Go's standard library, and run by using the test command. Note that you must implement your tests in a file whose name ends in _test.go, and you must put it in the same package of the file it tests (not in a separate xxx_test package).

D2. If your tests depend on external components (e.g. a database, network, etc.), the calls to these dependencies should be properly separated from the main code by an interface, and the interfaces mocked to simulate the external component during tests.

D3. The tests should be easily executable and automated. Example or a working Make target used in several projects, that allows to execute tests:

test_local:
	go test -v -race -short -p=1 ./...

Versioning

E1. Go modules must comply to Semantic Import Versioning in order to be importable. Note that this is particularly important for versions >= v2, as this requires changes in the go.mod and source files (see point E2).

E2. Before releasing a new version, follow the guide on how to prepare new releases.

Useful tools

  • Mocking frameworks that may be used for Go projects are gomock and pegomock.

  • A TOML parser that may be used in Go applications is go-toml.

  • A lexer/parser that may be used to make more robust the parsing of the CLI arguments of Go applications is goyacc.

  • Packages that may be used to perform logging in Go applications are log, logrus and, for distributed applications, trace.

References

Last updated