Go 🐹

Hero image for 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

The team mainly uses 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.

    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
    }
    
    // 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.

    someErr := returnErrorFunc()
    
    err := returnErrorFunc()
    
  • Use lowercase with NO underscore for package names. Prefer conciseness over long names.

    package string_conversion
    package operatingSystem
    package documentation
    package system_log
    package io_utility
    
    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.

    var r *redis.Pool
    o := getOrders()
    u := findUser(i)
    
    for index, customer := range customers {
        ...
    }
    
    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
    )
    
    import (
        "errors"
        "nimblehq/controllers"
        "net/http"
        "github.com/fizz/buzz"
        "nimblehq/models"
        "github.com/foo/bar"
    )
    
    import (
        "errors"
        "net/http"
    
        "nimblehq/controllers"
        "nimblehq/models"
    
        "github.com/fizz/buzz"
        "github.com/foo/bar"
    )
    
  • Sort packages by alphabetical order for each group.

    import (
        "net/http"
        "errors"
    
        "nimblehq/models"
        "nimblehq/controllers"
    )
    
    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.

    import (
        "errors"
        goUrl "net/url"
    
        errors_config "mypackage/config/errors"
        "mypackage/config/url"
    )
    
    import (
        "errors"
        gourl "net/url"
    
        errorsconf "mypackage/config/errors"
        "mypackage/config/url"
    )
    
  • Prefer naming modules in go.mod using the repository-hosting-domain/user-name/repository-name pattern.

    module my-project
    
    module github.com/nimblehq/my-project
    
  • Use this module name pattern convention when importing internal modules.

    import (
        "my-project/controllers"
        "my-project/models"
    
        "github.com/fizz/buzz"
        "github.com/foo/bar"
    )
    
    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.

    const failedStatus = "failed"
    const pendingStatus = "pending"
    const processedStatus = "processed"
    
    const (
        failedStatus = "failed"
        pendingStatus = "pending"
        processedStatus = "processed"
    )
    

Slices

  • Prefer var s []string over s := []string{} when declaring an empty slice.

Functions

  • Prefer declaring the type for each parameter over declaring a single type for all parameters.

    func fooBar(num1, num2 int, str1, str2 string) {
        // Some code
    }
    
    func fooBar(num1 int, num2 int, str1 string, str2 string) {
        // Some code
    }
    
  • Prefer unnamed return parameters.

    func fruit(name string) (color, taste string) {
        switch name {
        case "Banana", "Mango":
            color, taste = "Yellow", "Sweet"
        default:
            color, taste = "Unknown", "Unknown"
        }
        return
    }
    
    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.

    func validUser(u *User) bool {
        if u != nil {
            if u.Id != nil {
                return true
            } else {
                return false
            }
        } else {
            return false
        }
    }
    
    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.

    func (registration *Registration) New() {
        ...
        ...
    }
    
    func (serverHandler ServerHandler) ServeHTTP(rw ResponseWriter, r *Request) {
        ...
        ...
    }
    
    func (r *Registration) New() {
        ...
        ...
    }
    
    func (sh ServerHandler) ServeHTTP(rw ResponseWriter, r *Request) {
        ...
        ...
    }
    
    • Prefer placing all private functions after the public functions.
    func myPrivateFunction() {}
    
    func MyPublicFunction() {}
    
    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.

    if err := fooBar(); err != nil {
        // Error handling
    }
    
    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.

    func1Err := returnErrFunc1()
    if func1Err != nil {
        // Error handling
    }
    
    func2Err := returnErrFunc2()
    if func2Err != nil {
        // Error handing
    }
    
    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 the go 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
    
    // controllers/foo_test.go
    package controllers
    
    // controllers/foo_test.go
    package controllers_test
    
  • Any test function must have a Test prefix. The go 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 {...}
    
    Describe("Save", func() { ... })
    Describe(".Save", func() { ... })
    
    Describe("IsBlank", func() { ... })
    Describe("#IsBlank", func() { ... })
    
    Describe("#Save", func() { ... })
    
    Describe(".IsBlank", func() { ... })