The Coding Flow

Code Clean Or Die Tryin'

Learning to Go

In May I started into a breakneck adventure: new job, new programming language, new tech stack - everything starting over. I was hired at Paessler to be a Go developer. Coming from the Java planet, I had to learn everything from scratch:

  • the Go language itself
  • the “state-of-the-art” development environment for Go: VS Code
  • GitLab with its concept of CI/CD pipelines
  • Vue.js for some frontend work (see below)
  • Docker and Kubernetes as runtime environment

I can’t remember a moment in my career where I had to master so many different technologies at once. But “Viel Feind’, viel Ehr’”, so let’s rock!

Where to start?

The starting point for learning Go is definitively the Tour of Go. You get a rough overview of the language, can peek into all parts, and the online REPL environment allows you to already get your hands a bit dirty. This is like riding a bike with training wheels. But for the deep dive into a new language, I usually need two things:

  1. a good book
  2. a pet project

Reading about Go

I started the reading task with Programming in Go by Mark Summerfield. This book gives a step-by-step introduction into the language with only a few assumptions about the reader’s knowledge. It gives list-like overviews of some parts of the Go standard library. The fundamental concepts (e.g. the type system, packages, string handling, …) are explained in a very detailed way. The more practical and advanced things (concurrency, testing, low-level programming, code generation, the tool chain) are unfortunately scratched only on the surface or not mentioned at all. This book is certainly a good start for less experienced developers to learn the basics of Go, but I expected a little bit more.

The other book I read was The Go Programming Language by Alan A. A. Donavan and Brian W. Kernighan. Especially the latter author made me bit curious about the book. It also starts - as every programming language book - with an introduction to type system and control structures. Although this introduction is step-by-step, the authors use already in the beginning more advanced concepts in their code examples, which are then explained in detail in a later chapter. This is not a real problem if you just focus on the parts that are related to the current chapter and accept the other things as given. Besides the central topic of the current chapter, the code examples are also used to give an introduction to various parts of the Go standard library. After the basic chapters follows a very detailed description of concurrency programming in Go, either using CSP or the classical concepts like mutex. The book also contains chapters about the tool chain, testing, reflection and low-level programming. All in all I have the feeling that this book covers more ground in less pages.

Besides the books there are many online sources about specific topics, like the Go blog, the golang-nuts newsgroup, or JBD’s blog.

First Steps

For the first steps in a new programming language it is a good idea to have a pet project at hand. The problem domain should be well known or at least pretty simple so that you can concentrate on the language itself. And it should be a real-world problem where you need to provide a full solution. You might think about doing some katas or programming quizzes, but these usually only help you to focus on a few specific aspects. If you want to get a real feeling for the whole tool chain, library, ecosystem and so on, you need to build a complete solution from one end to the other.

I used to implement a bunch of tools and helpers for my hobby, amateur radio, in order to learn Python not long ago. So I decided that this will also be my training ground for the Go language and started already some time before my new engagement to re-implement some of the Python tools in Go. The command line tools I developed so far provide distance calculations, access to several online databases and a more convenient way to use the cwdaemon. I even had a look into developing a desktop application using gotk3, which already looks quite promising.

When starting at Paessler, they also had a pet project for me. The scope was very narrow and clear, which allowed me to concentrate on the language and the other technologies that are important there and that I had not used before. It took me about two month to implement everything involving classical database access, microservice architecture using the go-micro framework, release engineering in GitLab, and even a HTML5 frontend using vue.js. The microservices are deployed as a bunch of Docker images that run in a Kubernetes cluster. This was a very intense experience since really every single thing was new to me. I liked that very much!

How is it Going so far?

After several month using the Go language I can say that I really like it. Especially the following things are in my eyes remarkable:

Concurrency using Communicating Sequential Processes (aka Go routines)

The model of Communicating Sequential Processes (CSP) is Go’s main approach to concurrency. It is a simplification that restricts the possibilities that you have for concurrent task execution. Goroutines communicate through channels instead of using shared memory (“Don’t communicate by sharing memory; share memory by communicating. (R. Pike)”). This allows you to easily reason about what is happening - for each piece of data the processing still happens in sequence while the single steps work on a different piece of data in parallel. CSP encourages you to split up the data processing into several self-contained steps, which then can easily be distributed over multiple computing devices. Building processing pipelines from goroutines and channels feels a bit like the stream API concept that was introduced with Java 8.

Semantic Primitive Types

Go allows you to define new types based on the primitive types. A simple example: you want to handle temperatures and you define types for degree Fahrenheit and for degree Celcius. The compiler will not allow you to assign a Fahrenheit value to a Celcius variable and vice versa, although they are based on the same primitive type:

type Fahrenheit float64
type Celcius float64

var f Fahrenheit = 451.0
var c Celcius

c = f // Compile Error!

Additionally you can define methods for any type, not only for structs. So you can have simple conversion methods like Fahrenheit(451).toCelcius().

Duck Typing

In Go, a type is considered to implement an interface as soon as the type provides all the methods defined in the interface. There is no need to declare a relationship between the type and the interface. You can introduce refined interfaces later into your system without the need to touch any existing type. In iterative software development it is very common that the structure of your system changes over time. With this feature, Go makes it easy to listen to your code and adapt the structure when needed.

Table-driven tests

The concept of table-driven test exists also in other languages and frameworks (e.g. parameterized tests in JUnit), but Go makes it very straightforward to implement by using an unnamed type:

func TestIToA(t *testing.T) {
    testCases := []struct { // unnamed type
        desc  string
        value int
        expected string
    }{
        { "1", 1, "1" }, // instance of unnamed type
    }

    for _, tC := range testCases {
        t.Run(tC.desc, func(t *testing.T) {
            assert.Equal(t, tC.expected, strconv.itoa(tC.value))
        })
    }
}

Another thing that’s handled a bit different in Go is that test cases usually do not stop when one assertion fails. Especially for table-driven tests this means that you get the results of all entries of the table even if the first row already failed. Of course, you can make a test stop immediately by implementing it, e.g. by using t.FailNow().

A language for grown-ups

The creators of the Go language were brave enough to make some bold decisions where other languages impose the freedom on you to discuss with your fellow colleagues about personal taste:

  • go fmt formats all Go code uniformly.
  • The opening curly brace is on the same line.
  • go fmt uses tabs for indentation.
  • The build fails if you were lazy: no unused imports or dead code allowed. There are some tricks to keep dead code, but they make it at least obvious that this was done on purpose.
  • Testing, benchmarks, coverage measurement, profiling, and a check for race conditions work out of the box, no additional library, tool, or framework is needed.
  • The go tool itself provides a full (basic) development environment, all you need is a text editor.

These decisions leave less space for personal taste and individuality - you either have to deal with it or leave. But there is also no need for endless discussions about personal taste and individuality, which frees a lot of energy that can be used to solve real problems. So if you are tired of the endless discussions, Go is the language for you.

Missing Something?

Coming from planet Java, I’m very fastidious as far as refactoring tools are concerned. In Eclipse there are plenty of refactorings available for Java, they work even if your code is currently failing to compile. The refactorings work fast and reliably with full support to undo them if they did not bring the expected result.

What I found about refactoring Go is a bit sobering. VS Code can do a rename for you, but it takes ages, your code must compile and there is no undo (at least not over several files). I also found a small number of other tools:

  • Go Doctor which supports also extract method/function, but it works on the command line.
  • go fmt allows to modify code - on the command line.
  • The eg command allows template-based refactoring - on the command line.

That’s it. Nothing as lightweight and intuitive as the tools I’m used to from Java, deeply integrated into the editor. So here is really a great opportunity to make a difference in the Go ecosystem.

Another thing that the Go language lacks is generics. For most things that were left out of Go by intention (e.g. enums), there exists a pattern to implement similar behavior in Go. For the case of generics I could not find one yet, at least no elegant one. This hinders me to express certain things in Go like orthogonality. The implementation of a sorting algorithm is orthogonal to the business needs and therefor should be implemented separately. Without generics this is difficult.

There is a big discussion in the Go community about generics, so I’m not the only one missing them. The current underlying implementation of Go’s type system is not in a shape to easily support generics, some major changes in the concepts need to be done. But since generics are a really powerful tool, I’m sure there will be something equivalent or even better at some day in Go.

The third thing that bugs me is one missing visibility scope. You cannot limit the visibility of a member to it’s enclosing type, everything is visible at least within the same package - or only in the current code block (and its sub-blocks). Something in between is missing. Without this type scope (aka. private) it is difficult to enforce encapsulation. It is too easy to just grab into an object and modify its state from the outside. If it is easy, sooner or later somebody will do it, no matter how many guidelines and how much reviews you have in your development process.

Conclusion

Go is not just the same things with another syntax or the academic testbed for a certain concept. The creators of Go built the language around a lot of practical experience. You can tell that by many details, for example they decided for one certain way to do something, where other languages give you several variants to choose from. They also tried to minimize the number of language constructs (e.g. only one keyword for loops) which makes it easier to learn and also easier to build tools for it. This makes Go a user-friendly programming language.

For me it was a great experience so far to dive into the Go universe. I got my head around it very fast and I’m looking forward to the coming 10 years to master this language as my new main work horse.