The Go language indisputably generates lot of discussions. Bjarne Stroustrup famously said:
There are only two kinds of languages: the ones people complain about and the ones nobody uses.
Many developers indeed share their usage retrospectives and the flaws they came to hate. No generics, no official tool for vendoring, built-in methods break the rules Go creators want us to endorse. The language ships with a bunch of principals and a strong philosophy.
Yet, The Go Gopher is making its way through companies. AWS is releasing its Go SDK, Hashicorp's tools are written in Go, and so are serious databases like InfluxDB or Cockroach. The language doesn't fit everywhere, but its concurrency model, its cross-platform binary format, or its lightning speed are powerful features. For the curious reader, Texlution digs deeper on Why Golang is doomed to succeed.
It is also intended to be simple. However, one should gain a clear understanding of the language's conventions and data structures before producing efficient code. In this post, we will carefully setup a Go project to introduce a robust starting point for further development.
Let's kickoff the work with some standard Go project layout. New toys in town try to rethink the way they are organized, but I like to keep things simple as long as it just works. Assuming familiarity with the Go installation and GOPATH mess, we can focus on the code's root directory.
➜ code tree -L 2
.
├── CONTRIBUTING.md
├── CHANGELOG.md
├── Gomfile
├── LICENCE
├── main.go
├── main_test.go
├── Makefile
├── shippable.yml
├── README.md
├── _bin
│ ├── gocov
│ ├── golint
│ ├── gom
│ └── gopm
└── _vendor
├── bin
├── pkg
└── src
To begin with, README.md, LICENCE and CONTRIBUTING.md are usual important documents for any code expected to be shared or used. Especially with open source, we should care about and clearly state what the project does, how it works and how one can (and cannot) use it. Writing a Changelog is also a smart step in that direction.
The package manager is certainly a huge matter of discussion among developers. The community was left to build upon the go get tool and many solutions arisen to bring deterministic builds to Go code. While most of them are good enough tools, Godep is the most widely used, but Gom is my personal favorite:
# Gomfile
gom 'github.com/gin-gonic/gin', :commit => '1a7ab6e4d5fdc72d6df30ef562102ae6e0d18518'
gom 'github.com/ogier/pflag', :commit => '2e6f5f3f0c40ab9cb459742296f6a2aaab1fd5dc'
Dependency groups
# Gomfile (continuation)
group :test do
# testing libraries
gom 'github.com/franela/goblin', :commit => 'd65fe1fe6c54572d261d9a4758b6a18d054c0a2b'
gom 'github.com/onsi/gomega', :commit => 'd6c945f9fdbf6cad99e85b0feff591caa268e0db'
gom 'github.com/drewolson/testflight', :commit => '20e3ff4aa0f667e16847af315343faa39194274a'
# testing tools
gom 'golang.org/x/tools/cmd/cover'
gom 'github.com/axw/gocov', :commit => '3b045e0eb61013ff134e6752184febc47d119f3a'
gom 'github.com/mattn/goveralls', :commit => '263d30e59af990c5f3316aa3befde265d0d43070'
gom 'github.com/golang/lint/golint', :commit => '22a5e1f457a119ccb8fdca5bf521fe41529ed005'
gom 'golang.org/x/tools/cmd/vet'
end
# install gom binary
go get github.com/mattn/gom
# ... write Gomfile ...
# install production and development dependencies in `./_vendor`
gom -test install
We just declared and bundled full requirements under its root directory. This approach plays nicely with trendy containers.
# we don't even need Go to be installed
# install tooling in ./_bin
mkdir _bin && export PATH=$PATH:$PWD/_bin
docker run --rm -it --volume $PWD/_bin:/go/bin golang go get -u -t github.com/mattn/gom
# asssuming the same Gomfile as above
docker run --rm -it
--volume $PWD/_bin:/go/bin
--volume $PWD:/app -w /app
golang gom -test install
An application can quickly rely on a significant number of external resources. Dependency managers like Gom offers a simple workflow to avoid breaking-change pitfalls - a widespread curse in our fast paced industry.
The ambitious developer in love with productivity can complete its toolbox with powerful editor settings, an automatic fix, a Go repl, a debugger, and so on. Despite being young, the language comes with a growing set of tools helping developers to produce healthy codebase.
With basic foundations in place, let's develop a micro server powered by Gin, an impressive web framework I had great experience with. The code below highlights commonly best practices one can use as a starter.
// {{ Licence informations }}
// {{ build tags }}
// Package {{ pkg }} does ...
//
// More specifically it ...
package main
import (
// built-in packages
"log"
"net/http"
// third-party packages
"github.com/gin-gonic/gin"
flag "github.com/ogier/pflag"
// project packages placeholder
)
// Options stores cli flags
type Options struct {
// Addr is the server's binding address
Addr string
}
// Hello greets incoming requests
// Because exported identifiers appear in godoc, they should be documented correctly
func Hello(c *gin.Context) {
// follow HTTP REST good practices with an adequate http code and json-formatted response
c.JSON(http.StatusOK, gin.H{ "hello": "world" })
}
// Handler maps endpoints with callbacks
func Handler() *gin.Engine {
// gin default instance provides logging and crashing recovery middlewares
router := gin.Default()
router.GET("/greeting", Hello)
return router
}
func main() {
// parse command line flags
opts := Options{}
flag.StringVar(&opts.Addr, "addr", ":8000", "server address")
flag.Parse()
if err := Handler().Run(opts.Addr); err != nil {
// exit with a message and a code status 1 on errors
log.Fatalf("error running server: %vn", err)
}
}
We're going to take a closer look at two important parts this snippet is missing : error handling and interfaces' benefits.
One tool we could have mentioned above is errcheck, which checks that you checked errors. While it sometimes produces cluttered code, Go error handling strategy enforces rigorous development :
type CustomError struct {
arg int
prob string
}
// Usage: return -1, &CustomError{arg, "can't work with it"}
func (e *CustomError) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.prob)
}
Interfaces in Go unlock many patterns. In the gold age of components, we can leverage them for API composition and proper testing. The following example defines a Project structure with a Database attribute.
type Database interface {
Write(string, string) error
Read(string) (string, error)
}
type Project Structure {
db Database
}
func main() {
db := backend.MySQL()
project := &Project{ db: db }
}
Project doesn't care of the underlying implementation of the db object it receives, as long as this object implements Database interface (i.e. implements read and write signatures). Meaning, given a clear contract between components, one can switch Mysql and Postgre backends without modifying the parent object. Apart from this separation of concern, we can mock a Database and inject it to avoid heavy integration tests.
Hopefully this tiny, carefully written snippet should not hide too much horrors and we're going to build it with confidence.
We didn't join a Test Driven Development style but let's catch up with some unit tests. Go provides a full-featured testing package but we are going to level up the game thanks to a complementary combo. Goblin is a thin framework featuring Behavior-driven development close to the awesome Mocha for node.js. It also features an integration with Gomega, which brings us fluent assertions. Finally testflight takes care of managing the HTTP server for pseudo-integration tests.
// main_test.go
package main
import (
"testing"
. "github.com/franela/goblin"
. "github.com/onsi/gomega"
"github.com/drewolson/testflight"
)
func TestServer(t *testing.T) {
g := Goblin(t)
//special hook for gomega
RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) })
g.Describe("ping handler", func() {
g.It("should return ok status", func() {
testflight.WithServer(Handler(), func( r*testflight.Requester) {
res := r.Get("/greeting")
Expect(res.StatusCode).To(Equal(200))
})
})
})
}
This combination allows readable tests to produce readable output. Given the crowd of developers who scan tests to understand new code, we added an interesting value to the project.
It would certainly attract even more kudos with a green test-suite. The following pipeline of commands try to validate a clean, bug-free, code smell-free, future-proof and coffee-maker code.
# lint the whole project package
golint ./...
# run tests and produce a cover report
gom test -covermode=count -coverprofile=c.out
# make this report human-readable
gocov convert c.out | gocov report
# push the reslut to https://coveralls.io/
goveralls -coverprofile=c.out -repotoken=$TOKEN
Countless posts conclude this way, but I'm excited to state that we merely scratched the surface of proper Go coding. The language exposes flexible primitives and unique characteristics one will learn the hard way one experimentation after another. Being able to trade a single binary against a package repository address is such an example, like JavaScript support.
This article introduced methods to kick-start Go projects, manage dependencies, organize code, offered guidelines and testing suite. Tweak this opinionated guide to your personal taste, and remember to write simple, testable code.
Xavier Bruhiere is the CEO of Hive Tech. He contributes to many community projects, including Occulus Rift, Myo, Docker and Leap Motion. In his spare time he enjoys playing tennis, the violin and the guitar. You can reach him at @XavierBruhiere.