Three of our engineers Joseph Woodward, Nikolai Vladimirov and Mihai Tiriplică tell their stories about transitioning to writing Go full time. Each of our engineers comes from a different background: .NET/C# , Python and Ruby.
The discussion covers a wide variety of language aspects: error handling, strong typing, simplicity, speed & concurrency, building, and dependency management.
Our engineers compare a wide variety of Go language aspects with their experiences of their previous programming language. Here are some of the key highlights of their discussion.
In Go, error handling is explicit and errors are handled as part of normal execution flow.
When first starting out with Go, error checking explicitly using if err != nil seemed cumbersome, but it does provide an improvement in readability in the long run. Code is more often read than written, after all. Go’s explicit error handling, does make the code more verbose, but it makes us consider our error cases at pull request or design phase.
.NET uses exceptions for error handling. In particular, .NET has opaque error handling as exceptions do not need to be declared on the function signature, as is required with Java exceptions. This can make it quite difficult to find out where an exception is coming from in production.
Ruby exceptions are typically wrapped, although this is not enforced by the language itself. The custom exceptions are raised in a similar way to C# and Java.
Go is a strongly typed language, where type checking and enforcement happens on the compiler level.
On the other hand, Python is a dynamically typed language, where type checking only happens at runtime.
Go is considered a small language as it has a reduced amount of keywords.
Go’s simple syntax and way of solving problems makes it easy to understand code across codebases and even organisations, such as opensource. The Go toolchain includes gofmt, which ensures that Go code looks the same and is easy to read.
In contrast with .NET that has a lot of functionality, Go still provides a good balance of abstraction on top of the simple building blocks that it provides.
Dealing with lower level primitives makes it easier to pick up and understand as it does not provide any “magic code” that is so abstracted it is hard to understand.
Python is very easy to start with, as it’s a very flexible language. The flexibility of Python is a double edge sword as the flexibility makes it difficult to figure out how to actually start implementing a new project. Go does not have these issues, as you can solve problems in a reduced number of ways.
Ruby leverages the power of metaprogramming, which produces very clear and simple business logic. On the other hand, the code is difficult to understand and appears magic. 🪄
.NET uses asynchronous programming with async and await for concurrent execution.
This can be quite difficult to read code, as you need to decorate your code with the two keywords and reason about execution order. Furthermore, changing a function to be asynchronous ripples up all the way to the top level.
Python has a global interpreter lock (GIL) which does not allow you to run on multiple cores. In Python, fan out is achieved with multiple processes. Moving to Go meant that you could reduce your Kubernetes cluster and use of resources by a lot, as you begin to leverage concurrency.
Ruby is an interpreted language that runs in a VM. This makes it suffer in terms of performance and concurrency.
Finally, our speakers all agree that the compile times in Go are also very quick, giving engineers a quick feedback loop.
In Go, building is part of the standard toolchain. Everyone builds their code in the same, standard way. This makes it easier to download and contribute to other projects.
.NET has MSBuild, which is defined in XML.Projects are not guaranteed to build even though they have one build system.
In Python, most tools are inbuilt, but there are a lot of alternatives too.
In Ruby, most of packages usually work. However, you do need to match the version of the ruby interpreter and the package version, as a lot of breaking dependencies were introduced. There are tools to manage multiple Ruby versions, but the Go promise of backwards compatibility is safest.
Go modules deals with dependency management. Projects define and import modules as their dependencies. The code that you dependent on is pulled into your code.
In .NET, you import DLLs as your dependencies, making it hard to see the code in your dependencies.\ Coming from a .NET background, it was a huge game changer to be able to look at the code in your dependencies with ease in Go.
In Python, the package manager pip manages and installs dependencies.
If you enjoyed this episode and would like to be part of the podcast, then please fill in this form and we’ll be in touch. ✍️
Here are some other resources that you might find interesting:
Bartłomiej Klimczak, a Go developer at G2A talks us through what it is like to work in the Go ecosystem. Having developed a love for the programming language, Bart runs the GoKraków meet-ups in Poland.
In this episode, we introduce our new .tech series led by our Head of Platform Engineering, Kevin Holditch. Kevin tackles how, why and when you should move to Go with the help of Johan Brandhorst & Andy Kuszyk.