Bisma Pervaiz April 18, 2023

Transitioning to a new programming language can be challenging, and Go is no exception. In this article, I’ll explore some of the challenges I faced when migrating to Go from another language. 

parrish-freeman-4rF9RftX4XA-unsplash-scaled

Golang, aka Go, is a modern programming language known for its simplicity, ease of use, and ability to handle high-concurrency tasks. However, this simplicity can sometimes bring complexity to the code you write. As a software developer, I learned this while delving deeper into coding with Go. 

Navigating the Unintended Complexity in Go

Go-ing Beyond Static Typing: Implementing Polymorphism

The most prominent area (in my experience) where Golang's simplicity can cause complexity is in polymorphism. Other languages, like .NET, easily implement polymorphism using classes that inherit from a base class. But in Go, things work a little differently.

While Go has polymorphism, it is limited to interfaces only. This means developers have to create interfaces for every structure they build and make sure each class implements those interfaces. This introduces complexity when dealing with polymorphic JSON structures.

I encountered complexity while implementing templates for generating APIMatic’s Golang client SDKs. API definitions with inheritance in their model structures require generated code that supports inheritance and polymorphism. It was a challenging task to work with arrays of polymorphic objects, but I discovered that using custom unmarshaling with a discriminator could help. This discriminator tells the unmarshaller which type of object it is dealing with, so it can convert the interface type into the correct object type and then call that object unmarshaller. unmarshal it.

The Great Error Hunt in Go

Beyond polymorphism, there are other factors making the switch to Go a challenge. For example, Go has a unique style of handling errors. Instead of throwing simple exceptions like other languages, Golang uses multiple return values and error codes. This means an error propagates till it reaches the code that invoked it. This approach leads to more explicit error handling, but it is more verbose and requires the implementation of additional error-handling logic. 

To illustrate, let’s look at some code with error handling.

// GetLocationById returns Location Coordinates
// if the ID provided is valid else returns an error.
func GetLocationById(id int) (*Coord, error) {
if id > 0 && id < 10 {
return &Coord{1, 2}, nil
}
return nil, errors.New("unknown id")
}

func main() {
coord, err := GetLocationById(344)
if err != nil {
fmt.Println("An error occurred: ", err)
return
}
fmt.Println("Got the coordinates: ", coord)
}

Smelly Garbage Collection in Go

Among popular server-side programming languages, Go is unique in its use of a garbage collector to manage memory. This is an example of Golang providing simplicity to developers. While garbage collection makes memory management easier for developers, it also exposes risks of performance issues. If not managed properly, garbage collection can introduce additional overhead and latency. This is complexity for developers who write low-latency applications. 

Perils of Go’s Dependency Management System

Go does not have a central dependency management system like other languages, and developers typically load dependencies straight from GitHub or any public platform. This makes it simple to work with, but you may spend more time searching for the right package to use.

It isn’t all bad..

While Go's simplicity can create complexity in some areas, there are still many things to love.

Simplicity in Access Modifiers

I appreciate Golang’s simplicity when it comes to variable access modifiers. Unlike other languages, there are no specific access modifier keywords in Go. Instead, Go has a simple approach to access modifiers: capitalized means public and un-capitalized means private (outside package). This makes it easy to understand and manage the visibility of your code. Here’s an example of how simple access modifiers are in Golang:

// There are only 2 access modifiers in Go; Exported(public) and Unexported(private).
type Sample struct {
PublicField bool // Public/Exported fields in Go have capitalized field names
privateField string // Private/Unexported fields in Go have lowercased first letter of the field name
}

This simple naming convention ensures their code is easy to read and maintain, while still maintaining strong encapsulation.

Go Generics: The Gift that Keeps on Giving

In early 2022, Golang developers released Go generics, which takes a lot of boilerplate code away, making code simpler and easier to work with. This feature allows developers to work with any type of data in a more efficient way.

Go’s Multiple Returns for Cleaner Code

One of the distinctive features of Go is the ability to return multiple values from a function. This allows developers to write clean and concise code that can handle multiple errors or return multiple values without having to resort to using structures or other data types.

This feature is particularly useful in error handling, where a function can return both a result and an error value at the same time, reducing the need for error-checking code throughout the application.

type Coord struct {
Latitude int
Longitude int
}
// GetLocationById returns Location Coordinates
// if the ID provided is valid else returns an error.
func GetLocationById(id int) (*Coord, error) {
if id > 0 && id < 10 {
return &Coord{1, 2}, nil
}
return nil, errors.New("unknown id")
}

Code Styling is FTW!

Go has a built-in tool called gofmt, which automatically formats code according to Go conventions. This makes it easy for developers to write clean, consistent code without having to worry about manually formatting it. Additionally, the Go vet tool can help identify subtle issues in code such as unreachable code areas. This helps developers catch errors early on and ensures their code adheres to Go's strict coding standards.

Go-odbye Unused Variables 

One of the key benefits of Go is its efficient memory management. Golang's compiler does not allow unused variables in the code, which saves a lot of memory. This means developers cannot accidentally declare dead variables in their code which would consume memory. This helps reduce memory usage and ensure your Go applications run efficiently.

It's Time to Go Now

Working with the Go programming language has come with its fair share of challenges. However, I've relished the simplicity and efficiency that this language offers, enabling me to tackle complex tasks with ease. Now, with the power of Go generics, I'm beyond excited to get my hands on this new toolset, which I believe will revolutionize the way I approach coding.

If you are interested in creating Go SDKs for your API, check out the blog post about our new APIMatic Code Generator for Go where we walk you through the design and developer experience of our generated Go SDKs.