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)
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
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
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:
Shopspring / decimal package
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
}
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
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