Beginning Go: The experience

For building Bamboo microservices, we shortlisted on Go. We wanted a compiled language that is quick to develop and easy to maintain. This post seeks to capture the nuances of learning Go, from a programmer coming from a background in Java and Node.

GOPATH

The first thing every new Go developer notices is that you don’t have the freedom to keep Go code in any folder that you like (well, you have alternatives, but they are not recommended). Instead, one needs to define a GOPATH environment variable, and Go source code go inside $GOPATH/src/path/to/package. Strange, isn’t it? Why would you want to keep your code inside a folder dictated by an environment variable?

The idea here is simplicity. In most languages dependencies are specified using a separate tool like Gulp or Gradle. In Go, since we have the whole code base under a well-defined path, dependencies are automatically linked from this path. GOPATH design basically exempts the need for build tool like Gulp or Gradle.

But…

Dependencies are not always the master version! While when we do a go get, the code-pull pulls the master version. How do we specify a particular version of the dependency?

We at Bamboo use the vendoring capability. In our Go project folder, we create a vendor/ directory where we place particular version of our dependency code. When we do a go build, the version of code inside the vendor/ directory takes precedence in the lookup over GOPATH one.

I have seen developers commit vendor/ directory in their Git repos. We don’t do it. Our vendor/ directory is in our .gitignore list. We use the dep tool for managing our dependencies and their versions. Command dep ensure downloads dependencies and populates the vendor directory.

It is strange, because GOPATH’s vision seems to solve the problem of dependencies, it doesn’t seem to be doing it comprehensively.

Build

One beautiful thing I love about Go is the way go command-line tool is developed. To build you just give go build. It builds the complete project. Note, it does not work on files, it works on the complete project. And doing a go install installs the compiled binary to your GOPATH/bin directory.

Our code projects are all Dockerized. Our eventual plan when we hit production is also to use Docker containers. But our developers use Mac. Being a compiled language, we had a problem: building Go binaries in Mac and packaging them inside Linux containers. To my surprise, Go solved it elegantly: compile to Linux binaries in Mac!

The command that we use:

GOOS=linux GOARCH=amd64 go build

And voila! We get the Linux binary to be packaged inside the container!!

Public / Private

Private and public visibility scope is defined by how the function / variable / struct names start with. Uppercase first letter means a public scope, and lowercase means private scope. Elegant isn’t it? No additional keywords for scope management. The language remains simple!

Package namespace

All the languages I have been exposed to, one thing is common: you have packages, and files inside them. And each file inside package has its own namespace, and can share same function names in each of them. Not it Go. Even if you plan on organizing your code inside a package in multiple files, there cannot be duplicate names, private or public within those files.

Style of programming

Having come from a background of Node, I saw my coding style getting influenced by callback model. There was an old benchmark that suggested closures being slow in Go. When I ran the same benchmark again, in Go 1.8, there seems to be no difference. While doing this, it occurred to me, why not follow the Go procedural model of programming? Why carry on this legacy from Node? So now my code is purely procedural with multiple return values, error being one of them.

acc, err := mypkg.CreateAccount()

Variable declaration: name, then the type

In traditional languages like C, the variable declaration is like:

int i;

This is not the case with Go. We declare variables like:

var i int

This probably was the most frequent mistake I made when I started writing Go code, declaring variables like C :-) Well, there are few reasons for this inversion. One of the reasons I liked is, when translating this to plain English, the flow seems to be more natural:

var i int: Variable i of type Integer.

func x(y int) int {…}: Function x taking y as Integer parameter returning Integer.

Go Rules

Learning a new programming language once in a while is like doing an introspection. There are many practices that we take for granted just because we are used to that. A beautifully designed language like Go helped us challenge those habits of ours. Let us know your experience with Go in the comments.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.