Go has gained widespread adoption over the last few years. It was developed at Google to solve Google’s internal problems with large-scale infrastructure projects. As a programming language it was deliberately designed to be simple, safe and opinionated. It has concurrency built in and its libraries are written for high-performance I/O.
We used Go in different projects and want to highlight some challenges you’ll probably encounter when using it in software projects. We do this by discussing the most important language properties, the toolchain and the surrounding ecosystem.
Error handling is simple and clear, but not concise. Errors are values, employing the fact that functions can return multiple values. Therefore, a caller has to check for any Errortype return value after each function call which might return an error. There are patterns to ease the pain of typing endless if err != nilchecks, but you still have to implement those yourself.
People coming from programming languages with exception support often dislike the Go style of error checking. When compared to exception handling, explicit error checking exposes a more clear control flow. The resulting code is considered to be easier to reason about. This approach to error handling encourages developers to handle an error where and when it may occur. In turn that is expected to result in more stable software. However, there is a downside: You can still lose errors, either by not handling any return values of a function call or by explicitly ignoring them with the blank identifier.
Concurrency in Go is provided by CSP-inspired goroutines and channels. These are simple yet powerful means to build highly concurrent applications. Nonetheless, in real-world use cases they are not exactly easy to use: Since a created goroutine does not return a handle to cancel them, you often end up with an extra channel to control the goroutine’s lifecycle in addition to the actual message channel. Moreover, the standard library only provides primitives like mutexes. So you might be tempted to look for a library or to roll your own higher-level abstractions which comes with its own risks. Fortunately Go provides a built-in heuristic based race detectorwhich is easy to use and should be enabled in your setup.
The Type System favours composition over inheritance, thus enabling approachable and maintainable designs. Interfaces provide polymorphism via structural typing so you can easily decouple incoherent components.
Fully-fledged parametric polymorphism, i.e. generics, had been deemed expendable by the Go core team for a long time. To some extent generic programming is possible by using the built-in map and slice data structures which feature parametric polymorphism. You can combine that with either manual type assertions or code generation, but doing so can be cumbersome.
The Toolchain comprises everything needed in the development lifecycle (e.g. package management, building, testing, linting, ...) in one gobinary. It is reasonably easy to use and well suited for scripting. A special mention is deserved for go modwhich allows for vendoring dependencies without the need for third party tools. If you need vendoring you should definitely have a look at this sub command.
Finally, Go’s Ecosystem can be described as slowly maturing. The quality of publicly available libraries varies greatly. Many of them are tied to special use cases of the original authors, others are abandoned since they just served as learning playground. So some digging is required. It’s definitely worth to check the activity and review the source code before adding a project as a dependency.
We’d recommend using Go when implementing CLIs and network/infrastructure tooling, a field formerly known as systems programming. Here Go really shines with its concurrency model and low-level network support. Especially CLIs benefit from Go’s static binaries and multi-platform support.
We’d not recommend writing complex business services in Go. Our observation is that in most such projects Go had been chosen for the wrong reasons. If your first concern is performance when implementing a business service, you probably fall into the premature optimization trap. When you just think that Go is cool because it comes from Google and everyone and their dog uses it, then keep in mind that “Go is a programming language designed by Google to help solve Google's problems” (Rob Pike) and ask yourself whether you face the same problems. Still, it’s perfectly possible to implement business services in Go: It just requires a lot of experience and strict discipline, i.e. a seasoned and stable team. Alternatively, you could also wait for Go 2 which is expected to address error handling, error value semantics, and generics.