.tech Podcast - Moving to Go

Three of our engineers Joseph WoodwardNikolai 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.

Error handling

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.

Strong typing

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.

  • Python excels at data handling and transformation problems, where the data types are not always as expected.
  • Strongly typed languages are safer and easier to use in bigger codebases. The typing system helps engineers know what the method is expecting and what values to pass to it to avoid errors.
  • Python engineers sometimes enforce types using tests, which is cumbersome and time consuming.

Simplicity

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. 🪄

  • As the project grows, it can become very difficult to debug and trace.
  • Ruby is simple to start with, but requires senior expertise to ensure things don’t go wrong at scale.

Speed & concurrency

One of the main advantages of Go is that it is very fast. It has explicit support for concurrent programming, so synchronisation is native and easy to use with channels and goroutines.

.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.

  • Running concurrent Ruby is done in the JVM with JRuby.
  • The Ruby community efforts shifted to running delayed job execution tools, as it was difficult to run things concurrently. There are advanced and easy to use tools to run background jobs.
  • Go’s channels and goroutines are easy to use and remove these problems that Ruby suffers from.

Finally, our speakers all agree that the compile times in Go are also very quick, giving engineers a quick feedback loop.

Building

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.

This is not always the case in other languages. For example, Java has multiple solutions such as GradleAnt and more!

.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.

  • There are in built test runners, but there are other libraries which can be used as well.
  • Styling and formatting tools are not inbuilt.

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.

Dependency management

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.

  • The big difference is that Go modules only installs dependencies on the project level, whil pip does install them globally leading to version issues.
  • However, the big downside to using the GitHub backed Go modules is that it can be hard to keep track of supported versions, as libraries are being pulled on the fly.

In Ruby, dependencies are managed with RubyGems and bundler.

  • You can specify versions for development versus prod, as well as what to load with tests.
  • There is support for multiple versions as well.

Interested in being a guest speaker?

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. ✍️

by Adelina Simion Technology Evangelist

Further resources

Here are some other resources that you might find interesting:

.tech Podcast - Working in the Go ecosystem

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.

.tech Podcast - How and why you should move to Go

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.

.tech Podcast - How do interpreters work?

Join host Kevin Holditch for a podcast episode on how interpreters work. Thorsten Ball, author of the fantastic book - Writing An Interpreter In Go - lifts the bonnet on what happens inside the interpreter.