Hi everyone! This is the second part of the article about common go lang patterns.
You can find the first part here.
One liner if statement
if err := someOperation(); err != nil {
// error handling here
}
I personally do not use it that much, but it can save a couple of keystrokes if all you want to do is returning the error upstream.
if err := someOperation(); err != nil {
return errors.Wrap(err, "Ops, unable to complete some operation")
}
Walker
I have seen this one used pretty much in all the codebases I stumbled upon.
Most notably, it is used in the path/filepath package.
The need is similar to the one addressed by the iterator pattern:
- decoupling the algorithm from the data structure implementation
- separate the logic necessary to iterate over data from the one which acts on it
func WalkInWordGrams(walker func(wordgram *wordgrams.WordGram) error) error {
// Iteration happens here
}
WalkInWordGrams(func(wordGram *wordgrams.WordGram) error {
for _, stats := range wordGram.Stats() {
// ... do something meaningful with stats
}
return nil
})
In the code above we call a function WalkInWordGrams
which takes as parameter a lambda which process data.
The lambda func is called for each element in the data structure.
In case of an unrecoverable error, we can stop the iteration by returning it.
Using what we know from the previous article, we can improve our DSL by adding a type for the lamdbda function.
type WordgramWalker func(wordgram *wordgrams.WordGram) error
func WalkInWordGrams(walker WordgramWalker) error {
// Iteration happens here
}
WalkInWordGrams(func(wordGram *wordgrams.WordGram) error {
for _, stats := range wordGram.Stats() {
// ... do something meaningful with stats
}
return nil
})
Test data
This is something pretty useful when writing tests.
Go build ignores directory named testdata
and when it runs tests it sets current directory as package directory. This allows you to use relative path testdata directory as a place to load and store our data.
For example we can write an utility function like the following:
func LoadTestFile(name string, failureHandler func(message string)) []byte {
path := filepath.Join("testdata", name)
bytes, e := ioutil.ReadFile(path)
if e != nil {
failureHandler(e.Error())
}
return bytes
}
The function reads the file from testdata and returns it to the caller.
In case it fails, it calls the failure handler.
We can use it like this:
import (
"bytes"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"io/ioutil"
"net/http"
"path/filepath"
"testing"
)
func Test(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Test suite")
}
var _ = Describe("Some test which requires a file", func() {
It("does what is supposed to do", func() {
image := LoadTestFile("amalfi-coast.jpg", Fail)
...
Godoc Example
This is something pretty cool, which helps to improve docs for our code.
From the golang docs:
Godoc examples are snippets of Go code that are displayed as package documentation and that are verified by running them as tests. They can also be run by a user visiting the godoc web page for the package and clicking the associated "Run" button.
Writing one is really easy. We need to write a function which name starts with Example
in a test file.
func ExampleChain() {
endpoint, _ := chain(
loadEndpointFromConfigFile,
loadEndpointFromEnvVariables,
loadEndpointFromDatabase,
).get()
fmt.Println(endpoint)
// Output: some-endpoint
}
func loadEndpointFromEnvVariables() (string, error) {
return "", nil
}
func loadEndpointFromConfigFile() (string, error) {
return "", nil
}
func loadEndpointFromDatabase() (string, error) {
return "some-endpoint", nil
}
The cool part is that the example becomes a test.
The Output comment
is used to verify that the output matches our expectations.
It does so, by capturing data written to standard output and then comparing the output against the example's "Output:" comment. The test passes if the test's output matches its output comment.
Conclusions
Thanks for reading!
If you think I am missing some interesting pattern, let me know!
Top comments (2)
I'm a major novice (and not in the better way of interpretation) in Go, but to clarify:
in your first code block, you had not set up the WordgramWalker type, so you couldn't use this could you? You would have had to had the func defined as:
Or something along these lines?
Really interesting read, I just wanted to make sure I understood :D
You are right, nice catch.
The example would fail to compile, because WordgramWalker is not defined.
The Ctr+C && Ctr+V pattern failed me XD
Thanks for reading and letting me know about the mistake!
I have updated the example with the correction :)