Go 🐹
The Go language comes with its own sets of recommendations. Nimble general code conventions apply when they do not go against these recommendations.
Linting
We mainly use the built-in command go vet
for automated code checks.
Formatting
- Use a tab of 4 spaces for indentation.
- Format the syntax using
go fmt
. - Use a single blank line to break between statements and organize logical chunks of code.
- End each file with a newline.
Naming
-
Use UpperCamelCase for public and lowerCamelCase for private interfaces, structs, variables and functions.
// Bad type hello_world interface{} type fizz_buzz struct{} type HtmlEvent struct{} var my_array []string my_array := []string{} func foo_bar() { // some code } func PublishApi() { // some code } // Good // public type HelloWorld interface{} type FizzBuzz struct{} type HTMLEvent struct{} // private var myArray []string myArray := []string{} func fooBar() { // some code } func publishAPI() { // some code }
-
Use
err
as a common variable name for error type.// Bad someErr := returnErrorFunc() // Good err := returnErrorFunc()
-
Use
lowercase
with NO underscore for package names. Prefer conciseness over long names.// Bad package string_conversion package operatingSystem package documentation package system_log package io_utility // Good package strconv package os package doc package syslog package ioutil
-
Use meaningful variable names for global/local/argument variables and single-letter names for loop variables.
// Bad var r *redis.Pool o := getOrders() u := findUser(i) for index, customer := range customers { ... } // Good var RedisPool *redis.Pool orders := getOrders() user := findUser(userID) for i, c := range customers { ... }
Imports
-
Organize packages in groups, separated by a blank line between each group.
import ( // Standard library packages // Internal project packages // Third-party packages )
// Bad import ( "errors" "nimblehq/controllers" "net/http" "github.com/fizz/buzz" "nimblehq/models" "github.com/foo/bar" ) // Good import ( "errors" "net/http" "nimblehq/controllers" "nimblehq/models" "github.com/fizz/buzz" "github.com/foo/bar" )
-
Sort packages by alphabetical order for each group.
// Bad import ( "net/http" "errors" "nimblehq/models" "nimblehq/controllers" ) // Good import ( "errors" "net/http" "nimblehq/controllers" "nimblehq/models" )
-
Use
lowercase
with NO underscore to rename imported packages. This is often needed to avoid package import conflicts.// Bad import ( "errors" goUrl "net/url" errors_config "mypackage/config/errors" "mypackage/config/url" ) // Good import ( "errors" gourl "net/url" errorsconf "mypackage/config/errors" "mypackage/config/url" )
-
Prefer naming modules in
go.mod
using therepository-hosting-domain/user-name/repository-name
pattern.// Bad module my-project // Good module github.com/nimblehq/my-project
-
Use this module name pattern convention when importing internal modules.
// Bad import ( "my-project/controllers" "my-project/models" "github.com/fizz/buzz" "github.com/foo/bar" ) // Good import ( "github.com/nimblehq/my-project/controllers" "github.com/nimblehq/my-project/models" "github.com/fizz/buzz" "github.com/foo/bar" )
Declaration
-
Prefer grouping all Constants into a single block.
// Bad const failedStatus = "failed" const pendingStatus = "pending" const processedStatus = "processed" // Good const ( failedStatus = "failed" pendingStatus = "pending" processedStatus = "processed" )
Slices
- Prefer
var s []string
overs := []string{}
when declaring an empty slice.
Functions
-
Prefer declaring the type for each parameter over declaring a single type for all parameters.
// Bad func fooBar(num1, num2 int, str1, str2 string) { // Some code } // Good func fooBar(num1 int, num2 int, str1 string, str2 string) { // Some code }
-
Prefer unnamed return parameters.
// Bad func fruit(name string) (color, taste string) { switch name { case "Banana", "Mango": color, taste = "Yellow", "Sweet" default: color, taste = "Unknown", "Unknown" } return } // Good func fruit(name string) (string, string) { var color string var taste string switch name { case "Banana", "Mango": color, taste = "Yellow", "Sweet" default: color, taste = "Unknown", "Unknown" } return color, taste }
-
Prefer early returns to avoid deep nesting.
// Bad func validUser(u *User) bool { if u != nil { if u.Id != nil { return true } else { return false } } else { return false } } // Good func validUser(u *User) bool { if u == nil { return false } return u.Id != nil }
-
Prefer one or two letters abbreviation as the function receiver name. Such names should be consistent with the other receiver functions.
// Bad func (registration *Registration) New() { ... ... } func (serverHandler ServerHandler) ServeHTTP(rw ResponseWriter, r *Request) { ... ... } // Good func (r *Registration) New() { ... ... } func (sh ServerHandler) ServeHTTP(rw ResponseWriter, r *Request) { ... ... }
- Prefer placing all private functions after the public functions.
// Bad func myPrivateFunction() {} func MyPublicFunction() {} // Good func MyPublicFunction() {} func myPrivateFunction() {}
Errors
-
DO NOT ignore errors using
_
variables if the function returns an error. Check them to make sure the function is completed. -
Prefer declare then check over inline error handling for error returning functions.
// Bad if err := fooBar(); err != nil { // Error handling } // Good err := fooBar() if err != nil { // Error handling }
-
Prefer reusing the variable
err
instead of assigning errors to a new variable — errors must be handled right away.// Bad func1Err := returnErrFunc1() if func1Err != nil { // Error handling } func2Err := returnErrFunc2() if func2Err != nil { // Error handing } // Good err := returnErrFunc1() if err != nil { // Error handing } err = returnErrFunc2() if err != nil { // Error handing }
-
DO NOT use
panic
for handling normal errors. Use error and multiple return values.
Testing
-
Any test file must have a
_test.go
suffix as thego test
command automatically executes any file with names that match the file pattern*_test.go
. -
Prefer adding the
_test
suffix to the package name for any test package instead of using the same name as the original package. This naming keeps in sync the file name with the package name.// controllers/foo.go package controllers // Bad // controllers/foo_test.go package controllers // Good // controllers/foo_test.go package controllers_test
-
Any test function must have a
Test
prefix. Thego test
command automatically executes any function that matches this pattern.func TestXxx(*testing.T) { // Testing code }
-
Prefer describing method name with the
#
prefix and function name with the.
prefix in unit tests. This convention is only applicable when using the Ginkgo testing framework.// Go method func (u *User) Save() error {...} // Go function func IsBlank(value string) bool {...} // Bad Describe("Save", func() { ... }) Describe(".Save", func() { ... }) Describe("IsBlank", func() { ... }) Describe("#IsBlank", func() { ... }) // Good Describe("#Save", func() { ... }) Describe(".IsBlank", func() { ... })