My favourite Golang packages

Posted by Pavel on Thursday, June 18, 2020

Introduction

I have been working with Go language for about 5 years and during this time I have used different packages in my work or in my projects, which I enjoy to use at or which just help me in working with the code. In this article, I wanted to list the main my favorite universal packages.

Favorite packages

Testify (assert and require)

🔗Link

One of the commonplace but very convenient packages is the testify assert package. It allows you to simplify regular checks and make them one line

For example, if you want to compare the values of two unsorted slice you can simply run this command

package main

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestSlice(t *testing.T) {
	a := []string{"A", "B", "C"}
	b := []string{"B", "A", "C"}

	assert.ElementsMatch(t, a, b)
}

If you want to test the function and the error from it, and in case of an error fail the test, you need to use the assert package together with the require package:

package main

import (
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestFunction(t *testing.T) {
	surname, err := getSurname("Henry")
	require.NoError(t, err)
	assert.Equal(t, "Ford", surname)
}

func getSurname(name string) (string, error) {
	return "Ford", nil
}

In addition, in the same packages you can find a convenient mock. Use of these assert and require packages can be found in kubernetes code.

Davecgh / Spew

🔗Link

Spew is a convenient and simple package that helps me a lot during debug or to save data snapshots, in addition, it can be used for detailed logs. This package allows you to display the structure value in a readable format and at the same time expands the pointers inside the structure or slice.

Example of how it works:

package main

import (
    "fmt"
    "html"
    "net/http"

    "github.com/davecgh/go-spew/spew"
)

func handler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/html")
    fmt.Fprintf(w, "Hi there, %s!", r.URL.Path[1:])
    fmt.Fprintf(w, "<!--\n" + html.EscapeString(spew.Sdump(w)) + "\n-->")
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

Output:

(main.Foo) {
 unexportedField: (*main.Bar)(0xf84002e210)({
  flag: (main.Flag) flagTwo,
  data: (uintptr) <nil>
 }),
 ExportedField: (map[interface {}]interface {}) {
  (string) "one": (bool) true
 }
}
([]uint8) {
 00000000  11 12 13 14 15 16 17 18  19 1a 1b 1c 1d 1e 1f 20  |............... |
 00000010  21 22 23 24 25 26 27 28  29 2a 2b 2c 2d 2e 2f 30  |!"#$%&'()*+,-./0|
 00000020  31 32                                             |12|
}

Golangci-lint

🔗Link

Golangci-lint is not the library that you will use as a package in your application, but this linter will help you find poor formatting of your code or code that do not comply with the official code formatting recommendation. It seems to me especially this library is useful to those who came to Go from another language and are trying to write Go code with style of another language.

Example of how it works:

golangci-lint

Shopspring / decimal package

🔗Link

You probably know about floating-point arithmetic in software engineering. This “problem” can be found in Goland too:

package main

import "fmt"

func main() {
	a := 1.011
	b := a*1000
	fmt.Println(b)            // 1010.9999999999999

	fmt.Println(b == 1011)    // false

}

(Link to example)

To solve the problem of calculating and comparing numbers with a lot of decimal places you can use golang’s built-in math/big package or Shopspring/decimal package which generally uses same math/big library under the hood but makes writing code easier. Example of same calculation with decimal package:

package main

import (
	"fmt"

	"github.com/shopspring/decimal"
)

func main(){
	a := decimal.New(1011, -3)
	b := a.Mul(decimal.New(1000, 0))

	fmt.Println(b.String())							// 1011

	fmt.Println(b.Equal(decimal.New(1011, 0)))		// true

}

Of course, the code began to look not so simple, but if you work a lot with such numbers then accuracy is important to you. Or for example, you work with bitcoins, which can have up to 8 decimal places, then you must do your calculations with good precision.

Uber-go / Zap logger

🔗Link

Just very fast and convenient logger, example of how it looks like:

package main

import (
	"time"

	"go.uber.org/zap"
)

func main() {
	logger, _ := zap.NewProduction()
	defer logger.Sync()
	logger.Info("failed to fetch URL",
    // Structured context as strongly typed Field values.
    zap.String("url", "blogpavel.com"),
    zap.Int("attempt", 3),
    zap.Duration("backoff", time.Second),
	)
}

The main thing here is performance. Comparing performance of logging a message and 10 fields with other loggers:

Package Time Time % to zap Objects Allocated
⚡ zap 862 ns/op +0% 5 allocs/op
⚡ zap (sugared) 1250 ns/op +45% 11 allocs/op
zerolog 4021 ns/op +366% 76 allocs/op
go-kit 4542 ns/op +427% 105 allocs/op
apex/log 26785 ns/op +3007% 115 allocs/op
logrus 29501 ns/op +3322% 125 allocs/op
log15 29906 ns/op +3369% 122 allocs/op

comments powered by Disqus