Golang: 8 insights from the first weeks of the real usage

Igor Mandrigin
Go! Go! Go!
Published in
6 min readOct 7, 2015

I’ve been using Golang in a “real” project for a few weeks now. During the time I got some insights by making mistakes and trying to solve some typical problems using the language and the toolkit.

Here, I want to share these insights. I think they can be useful for the developers who are just beginning their path in Go programming. At least I wish I read something like this in advance ;)

Disclaimer: All the insights are Golang-specific. It is not a software development wisdom in general.

Let’s get started!

Insight #1: Absolute imports

The Golang imports system looked a bit unusual and unfamiliar to me at a first glance. To make it more similar to the languages I already new, I tried to use local imports in the project:

import (
"./model/account"
"./ui/accounteditor"
)

It did work… But as the project was growing, the imports became more and more messy, with lots of dots used:

import (
"./../../../model/accout"
"./../../accounteditor"
)

It looked suspicious, but it was still similar to some C++ code I`ve had experience working with. The real troubles hit me when I started covering my code with unit-tests. I got compile errors with the “local import in non-local package” message. After digging around and asking people, I came to the first insight: I shoudl always use the absolute imports.

import (
"myproject/model/account"
"myproject/ui/accounteditor"
)

Local imports might look more familiar, but they can be harmful in some situations. Use absolute imports.

Insight #2: How to structure a workspace?

So, I switched to the absolute imports.

The path you specify in the import statement is a relative path inside the $GOPATH/src directory, therefore it depends on how do you structure your workspace.

I came to the easy recipe to have a nice workspace if you are starting a new project:

  1. Create a new git repo.
  2. Commit a single .go file there.
  3. Use go get to actually check-out the repo.

If your GitHub repo is private or you have your local git server — go get seems to work fine with the certificate-based authentication.

This approach is wide-spread and used in the other companies.

If you use this approach, it is very easy to setup the new development environment on any machine or a CI server.

> go get myserver/path/to/repo
> cd myserver/path/to/repo
> go get ./...

It will install all the dependencies for you. This approach might not work that well if you don’t have a nice domain name for your git server (import statements will have IP addresses). In this case, it is usually a good idea to finally give a nice domain name to your Git server.

Insight #3: How to profile a webserver-based application?

Golang provides a nice runtime/pprof module for profiling. Use pprof.StartCPUProfile function to start the profiling and pprof.StopCPUProfile to stop it and write the data to the output file file.

Ok, I need to open a file, start profiling, then, just before exit, stop the profiling process and close the file. Sounds totally like a place to use the defer keyword, right?

Well, I tried it:

if f, err := os.Create(*cpuprofile); err == nil {
defer func() {
pprof.StopCPUProfile()
f.Close()
}()

pprof.StartCPUProfile(f)
} else {
log.Fatal(err)
}
// Start a web server

I started the app, loaded a few webpages and closed it with Ctrl+C… Surprise, surprise, the size of the output file was 0 bytes!

I experimented a little bit with a log messages, and figured out, that when you stop the server with Ctrl+C, defer-s are never got executed.

How do I fix it?

After digging in the internet I found a solution. One should create a goroutine to intersect the interrupt signal, to write the information to the file, close it and exit the app.

if f, err := os.Create(*cpuprofile); err == nil {
go func() {
sigchan := make(chan os.Signal, 10)
signal.Notify(sigchan, os.Interrupt)
<-sigchan
pprof.StopCPUProfile()
f.Close()
os.Exit(0)
}()
pprof.StartCPUProfile(f)
} else {
log.Fatal(err)
}
// Start a web server

This time it worked, and I got the data to analyze.

Insight #4: Do not embed an interface to a struct!

Golang FAQ recommends to implement an empty method Implements<InterfaceName> to explicitly show that the a implements the interface.

type MyInterface interface {
ImplementsMyInteface()
// other methods
}

type MyType struct {}
func (m MyType) ImplementsMyInterface() { /* do nothing */ }

I thought that it is an ugly solution. But sometimes it is really handy to show that this type implements this interface explicitly.

I just learned about the anonymous fields, and I thoughts that I can embed an interface as an anonymous field.

type Renderer interface {
CanRender() bool
Render() string
}
type TextRenderer struct {
Renderer
}
func TryRender(r Renderer) { ... }

This code looks really nice and readable, isn’t it?

It indeed does, but doing this leads to one very dangerous consequence.

By emdedding the interface in a struct you are telling the compiler that this struct implements the inteface. Compliler stops checking it at a compile-time..

In practice it leads to runtime panics instead of the compilation error if you will forget to implement one of the interface’s methods.

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0xffffffff addr=0x0 pc=0x20246]
goroutine 1 [running]:
main.(*TextRenderer).Render(0x1030a040, 0x1080, 0x0, 0x0)
<autogenerated>:2 +0x66
main.TryRender(0xfeec0018, 0x1030a040, 0x0, 0x0)
/tmp/sandbox287510054/main.go:8 +0x40

It is super easy to hit this error if are refactoring your code. Unit-tests will definitely help there, but Golang has such a good and strict compiler, so why not use it?

As a side note, the more I program in Golang, the less I want to explicitly specify interfaces.

Insight #5: Unit-testing

To run a unit-test against a package you just type:

go test package

How to run unit-tests recursively, on all of the packages of the app?

> go test ./...this is is not a typo, you have to have ./… as a parameter.

How to generate a code-coverage report in html?

> go test -coverprofile cover.out <project>/server/model
> go tool cover -html=cover.out -o cover.html

Insight #6: Dangers of the equality operator

Golang’s equality operator is good, and it works most of the time. It can even compare structs, which is very handy.

Unfortunately, under some conditions comparing structs’ equality with == may cause runtime panics like this:

panic: runtime error: comparing uncomparable type

There are two solutions to this problem:

  • Use reflect.DeepEqual().
  • Create an interface with the Equals(…) bool function.

I prefer the latter one. It requires a more boilerplate code, but it works better for complicated data types:

  • DeepEqual might be too slow;
  • if you have a fairly complicated data structure, you usually want to have a custom comparison (e.g. compare the only the IDs).

In my project I created the Equaler interface.

type Equaler interface {
Equals(Equaler) bool
}
// Example
type Entity struct {
Id string
}
func (self Entity) Equals(u equaler.Equaler) bool {
if other, ok := u.(Entity); ok {
return self.Id == other.Id
}
return false
}

Thus, as a rule of the thumb, don’t compare structs with the equality operator.

Insight #7: Use goimports and gorename!

Seriously, if you still not using these tools, you should start right now! They are awesome time-savers.

These commands are built in to the vim-go package I use, but you can use them even from the command-line.

Insight #8: gopkg.in

When your code depend on some external library, you usually want to keep the code of the library stable. Using go get <…>, will update the sources of the library every time you execute the command. This can be dangerous, because you can end up having different versions of the library on your development machine, your colleagues machines and your CI server. This can lead to hard-to-be-found bugs, crashes in production, etc.

There are 2 techniques to avoid this.

The first is called vendoring. There you have to copy the sources to the special directory inside your source tree called vendor.

The alternative is to use a nice web service called gopkg.in.

This service provides versioned package paths:

import "gopkg.in/user/pkg.v3"

This url (gopkg.in/user/pkg.v3) will return a branch or a tag called v3 or v3.N in the github.com/user/pkg repository.

Using this command not only can you keep the version of the library stable, you can also have multiple version of the same library in your workspace to use in different projects.

I hope these tips will be useful to you and will save you some time.

--

--

Igor Mandrigin
Go! Go! Go!

independent developer & security researcher with passion for blockchain: https://ffconsulting.org