Make sure your code passes the following tools:
go vet
gofmt -s
staticcheck
(viago install honnef.co/go/tools/cmd/staticcheck@latest
)
See our IDE/Editor setup doc for ways to doing that automatically.
Generated code (e.g. code generated by thrift compiler) are exempted from this rule.
Group your imports into the following 3 groups, in that order, separated by blank lines:
- Standard library packages
- Third-party packages
(non-stdlib packages without
github.com/reddit/baseplate.go
prefix) - Packages from the same module
(packages with
github.com/reddit/baseplate.go
prefix)
Example:
import (
"time"
"github.com/apache/thrift/lib/go/thrift"
"github.com/reddit/baseplate.go/log"
)
When an imported package name is different from what is implied by the final element(s) of the import path, rename the import it to make it explicit.
Example:
import (
jwt "gopkg.in/dgrijalva/jwt-go.v3"
opentracing "github.com/opentracing/opentracing-go"
)
The implied package name of all of the following is still "foo", and thus they do not need to be renamed:
.../foo.v1
.../foo/v1
.../foo-go
.../foo.go
Most import tools already understand these conventions.
When putting all args to a function signature/call to a single line makes the line too long, Use one-per-line style with closing parenthesis on the next line. Please note that in function signatures one-per-line means one group per line instead of one arg per line.
Example:
func foo (
arg1, arg2 int,
arg3 string,
arg4 func(),
) error {
// Function body
return nil
}
foo(
1,
2,
"string",
func() {
// Function body
},
)
When writing slice/map literals in one-per-line style, also make sure to put the closing curly bracket on the next line.
Example:
slice := []int{
1,
2,
3,
}
myMap := map[int]string{
1: "one",
2: "two",
}
When using slice literal/args as key-value map, use two-per-line style to make sure that we always have them in pair.
Example:
log.Errorw(
"Something went wrong!",
"err", err,
"endpoint", "myEndpoint",
)
Sometimes two consecutive lines could be on the same indentation level but not
the same logical level.
This usually happens when a long if
condition is broke into multiple lines.
In such cases,
please add a blank line between them to highlight the change of logical level.
Example:
if condition1 && condition2 &&
condition3 && condition4 {
// do something, note the blank line before this line.
foo()
}
Go allows multiple return values from a function and we should in general take advantage that, but the number of return values should be limited to a reasonable number. A rule of thumb is that if you ever have the need to split the return values into one-per-line on the callsite like this:
retval1,
retval2,
...
retvalN := package.FuncWithNReturnValues(a, lot, of, args, ...)
Then define a struct to return will be much better:
type FuncWithNReturnValuesResult struct {
Retval1 Retval1Type
Retval2 Retval2Type
...
RetvalN RetvalNType
}
func FuncWithNReturnValues(...) FuncWithNReturnValuesResult {
...
}
In general the error
return should not be part of the struct and still
returned as a separate and the last value. For example:
type FuncWithNReturnValuesResult struct {
Retval1 Retval1Type
Retval2 Retval2Type
...
RetvalN RetvalNType
}
func FuncWithNReturnValuesAndError(...) (FuncWithNReturnValuesResult, error) {
...
}
For things not covered above, use your best judgement and follow industry best practises. Some recommended resources are: