devz-docz

Aggregation of onboarding and general devz standards that I have gatherd over my career.

View on GitHub

Go

Overview

Go aka Golang is a programming language designed by folks at Google.

This guide provides some general resources for working in Go. Web Application in Go provides some specifics to web development.

Learning Resources

References

Tours/Lessons

Testing

General

Coverage

Packages

Prefer Standard Libraries

In general, when selecting new packages, highly consider standard libraries over third party dependencies. One of the strengths of Go is its core packages, such as http, json, and sql. These libraries also use vocabulary and patterns easily accessible via popular public Go resources, which are often translatable to modern programming approaches in their respective areas. This creates an easier bridge for Engineers new to Go or domain areas (such as relational databases) to adjust and onboard.

Since the third party ecosystem is still new, packages may have little community support, follow opinionated patterns inconsistent with Go idioms, or lack long term support. When choosing a third party package, carefully weigh the cost adoption and support contributions. Document the decision and any shortcomings of a comparable standard library along with a rollback plan.

Third Party Packages

Some examples of third party packages I’ve found to be helpful and stable are:

If you’re exploring a new package, Awesome Go is a good place to start.

Time

Clock Dependency

time.Now() can cause a lot of side effects in a codebase. One example is that you can’t test the “current” time that happened in a function you called in the past

For example, let’s say I have the following:

package mypackage

import "time"

func MyTimeFunc() time.Time {
    return time.Now()
}

func TestMyTimeFunc(t *testing.T) {
    if MyTimeFunc() != time.Now() {
        // This will error!
        // The time in the function and the test happen at different times
        t.Errorf("time was not now")
  }
}

How do I test the contents of the return here? If I want to assert the time I need a way to know what time.Now() was when the function was called.

Instead of directly using the time package, I can pass a clock as a dependency and call .Now() on that. Then in my tests, I can assert against that clock! The clock can be anything as long as it adheres to the clock.Clock interface as defined in the facebookgo clock package. I could, for example, make the clock always return the year 0, or the 2019 New Year, or maybe your birthday! In this clock package, there are two clocks.

Let’s look at the example above with the clock package.

package mypackage

import "fmt"
import "time"

import "gitlab.com/facebookgo/clock"

func MyTimeFunc(clock clock.Clock) time.Time {
    return clock.Now()
}

// Then my caller
func main() {
    // clock.New() creates a clock that uses the time package
    // it will output current time when .Now() is called
    fmt.Print(MyTimeFunc(clock.New()))
}

Then in my tests I can use a mock clock that freezes .Now() at epoch time:

func TestMyTimeFunc(t *testing.T) {
    testClock := clock.NewMock()
    if MyTimeFunc(testClock) != testClock.Now() {
        // both should equal epoch time, I won't hit this error
        t.Errorf("time was not now")
  }
}

Cool, but what if I want to use a different date? Say my test relies on my TestYear constant. The clock.Mock clock allows me to add durations to the clock and set the current time. Note that the clock.Clock interface does not allow this, it needs to happen before passing the mock clock through the interface parameter.

Setting the Mock Clock

Here’s an example using the test above and setting the time to September 30 of TestYear:

func TestMyTimeFunc(t *testing.T) {
    testClock := clock.NewMock()
    dateToTest := time.Date(TestYear, time.September, 30, 0, 0, 0, 0, time.UTC)
    timeDiff := dateToTest.Sub(c.Now())
    testClock.Add(timeDiff)
    if MyTimeFunc(testClock) != testClock.Now() {
        // both will now be September 30 of TestYear
        // I'll pass the test again
        t.Errorf("time was not now")
  }
}