Common anti-patterns in Go
Tags: go, programming, tutorial
Published: 2021-03-24 06:05:42

---
title: Common anti-patterns in Go
published: true
description: A list of anti-patterns you should avoid while writing code in Golang.
tags: golang, programming, tutorial
canonical_url: https://deepsource.io/blog/common-antipatterns-in-go/
---

It has been widely acknowledged that coding is an art, and like every artisan who crafts wonderful art and is proud of them, we as developers are also really proud of the code we write. In order to achieve the best results, artists constantly keep searching for ways and tools to improve their craft. Similarly, we as developers keep levelling up our skills and remain curious to know the answer to the single most important question - **“How to write good code”.** 🤯

Frederick P. Brooks in his book “ **The Mythical Man Month: Essays on Software Engineering** ” wrote :

> _“The programmer, like the poet, works only slightly removed from pure thought-stuff. He builds his castles in the air, from air, creating by exertion of the imagination. Few media of creation are so flexible, so easy to polish and rework, so readily capable of realizing grand conceptual structures._

![how-to-write-good-code.png](https://ucarecdn.com/76f98acb-ecc5-4701-9a61-409dc9062b4d/)

Image source: [https://xkcd.com/844/](https://xkcd.com/844/)

This post tries to explore answers to the big question mark in the comic above. The simplest way to write good code is to abstain from including **anti-patterns** in the code we write.😇

## What are anti-patterns? 🤔

Anti-patterns occur when code is written without taking future considerations into account. Anti-patterns might initially appear to be an appropriate solution to the problem, but, in reality, as the codebase scales, these come out to be obscure and add ‘technical debt’ to our codebase.

A simple example of an anti-pattern is to write an API without considering how the consumers of the API might use it, as explained in example 1 below. Being aware of anti-patterns and consciously avoid using them while programming is surely a major step towards a more readable and maintainable codebase. In this post, let’s take a look at a few commonly seen anti-patterns in Go.

### 1. Returning value of unexported type from an exported function

In Go, to `export` any `field` or `variable` we need to make sure that its name starts with an uppercase letter. The motivation behind exporting them is to make them visible to other packages. For example, if we want to use the `Pi` function from `math` package, we should address it as `math.Pi` . Using `math.pi` won’t work and will error out.

Names (struct fields, functions, variables) that start with a lowercase letter are unexported, and are only visible inside the package they are defined in.

An exported function or method returning a value of an unexported type may be frustrating to use since callers of that function from other packages will have to define a type again to use it.

```go
// Bad practice
type unexportedType string
func ExportedFunc() unexportedType { return unexportedType("some string")
} // Recommended
type ExportedType string
func ExportedFunc() ExportedType { return ExportedType("some string")
}
```

  

### 2. Unnecessary use of blank identifier

In various cases, assigning value to a blank identifier is not needed and is unnecessary. In case of using the blank identifier in `for` loop, the Go specification mentions :

> If the last iteration variable is the blank identifier, the range clause is equivalent to the same clause without that identifier.

```go
// Bad practice
for _ = range sequence
{ run()
} x, _ := someMap[key] _ = <-ch // Recommended
for range something
{ run()
} x := someMap[key] <-ch
```

  

### 3. Using loop/multiple `append`s to concatenate two slices

When appending multiple slices into one, there is no need to iterate over the slice and append each element one by one. Rather, it is much better and efficient to do that in a single `append` statement.

As an example, the below snippet does concatenation by appending elements one by one through iterating over `sliceTwo`.

```go
for _, v := range sliceTwo { sliceOne = append(sliceOne, v)
}
```

But, since we know that `append` is a `variadic` function and thus, it can be invoked with zero or more arguments. Therefore, the above example can be re-written in a much simpler way by using only one `append` function call like this:

```go
sliceOne = append(sliceOne, sliceTwo…)
```

  

### 4. Redundant arguments in `make` calls

The `make` function is a special built-in function used to allocate and initialize an object of type map, slice, or chan. For initializing a slice using `make`, we have to supply the type of slice, the length of the slice, and the capacity of the slice as arguments. In the case of initializing a `map` using `make`, we need to pass the size of the `map` as an argument.

`make`, however, already has default values for those arguments:

- For channels, the buffer capacity defaults to zero (unbuffered).
- For maps, the size allocated defaults to a small starting size.
- For slices, the capacity defaults to the length if capacity is omitted.

Therefore,

```go
ch = make(chan int, 0)
sl = make([]int, 1, 1)
```

can be rewritten as:

```go
ch = make(chan int)
sl = make([]int, 1)
```

However, using named constants with channels is not considered as an anti-pattern, for the purposes of debugging, or accommodating math, or platform-specific code.

```go
const c = 0
ch = make(chan int, c) // Not an anti-pattern
```

  

### 5. Useless `return` in functions

It is not considered good practice to put a `return` statement as the final statement in functions that do not have a value to return.

```go
// Useless return, not recommended
func alwaysPrintFoofoo() { fmt.Println("foofoo") return
} // Recommended
func alwaysPrintFoo() { fmt.Println("foofoo")
}
```

Named returns should not be confused with useless returns, however. The return statement below really returns a value.

```go
func printAndReturnFoofoo() (foofoo string) { foofoo := "foofoo" fmt.Println(foofoo) return
}
```

  

### 6. Useless `break` statements in `switch`

In Go, `switch` statements do not have automatic `fallthrough`. In programming languages like C, the execution falls into the next case if the previous case lacks the `break` statement. But, it is commonly found that `fallthrough` in `switch`-case is used very rarely and mostly causes bugs. Thus, many modern programming languages, including Go, changed this logic to never `fallthrough` the cases by default.

Therefore, it is not required to have a `break` statement as the final statement in a case block of `switch` statements. Both the examples below act the same.

Bad pattern:

```go
switch s {
case 1: fmt.Println("case one") break
case 2: fmt.Println("case two")
}
```

Good pattern:

```go
switch s {
case 1: fmt.Println("case one")
case 2: fmt.Println("case two")
}
```

However, for implementing `fallthrough` in `switch` statements in Go, we can use the `fallthrough` statement. As an example, the code snippet given below will print `23`.

```go
switch 2 {
case 1: fmt.Print("1") fallthrough
case 2: fmt.Print("2") fallthrough
case 3: fmt.Print("3")
}
```

  

### 7. Not using helper functions for common tasks

Certain functions, for a particular set of arguments, have shorthands that can be used instead to improve efficiency and better understanding/readability.

For example, in Go, to wait for multiple goroutines to finish, we can use a `sync.WaitGroup`. Instead of incrementing a `sync.WaitGroup` counter by `1` and then adding `-1` to it in order to bring the value of the counter to `0` and in order to signify that all the goroutines have been executed :

```go
wg.Add(1) // ...some code
wg.Add(-1)
```

It is easier and more understandable to use `wg.Done()` helper function which itself notifies the `sync.WaitGroup` about the completion of all goroutines without our need to manually bring the counter to `0`.

```go
wg.Add(1)
// ...some code
wg.Done()
```

  

### 8. Redundant `nil` checks on slices

The length of a `nil` slice evaluates to zero. Hence, there is no need to check whether a slice is `nil` or not, before calculating its length.

For example, the `nil` check below is not necessary.

```go
if x != nil && len(x) != 0 { // do something
}
```

The above code could omit the `nil` check as shown below:

```go
if len(x) != 0 { // do something
}
```

  

### 9. Too complex function literals

Function literals that only call a single function can be removed without making any other changes to the value of the inner function, as they are redundant. Instead, the inner function that is being called inside the outer function should be called.

For example:

```go
fn := func(x int, y int) int { return add(x, y) }
```

Can be simplified as:

  

### 10. Using `select` statement with a single case

The `select` statement lets a goroutine wait on multiple communication operations. But, if there is only a single operation/case, we don’t actually require `select` statement for that. A simple `send` or `receive` operation will help in that case. If we intend to handle the case to try a send or receive without blocking, it is recommended to add a `default` case to make the `select` statement non-blocking.

```go
// Bad pattern
select {
case x := <-ch: fmt.Println(x)
} // Recommended
x := <-ch
fmt.Println(x)
```

Using `default`:

```go
select {
case x := <-ch: fmt.Println(x)
default: fmt.Println("default")
}
```
  

### 11. context.Context should be the first param of the function

The context.Context should be the first parameter, typically named ctx. ctx should be a (very) common argument for many functions in a Go code, and since it’s logically better to put the common arguments at the first or the last of the arguments list. Why? It helps us to remember to include that argument due to a uniform pattern of its usage. In Go, as the variadic variables may only be the last in the list of arguments, it is hence advised to keep context.Context as the first parameter. Various projects like even Node.js have some conventions like error first callback. Thus, it’s a convention that context.Context should always be the first parameter of a function.

```go
// Bad practice
func badPatternFunc(k favContextKey, ctx context.Context) {	
    // do something
}

// Recommended
func goodPatternFunc(ctx context.Context, k favContextKey) {	
    // do something
}
```

When it comes to working in a team, reviewing other people’s code becomes important. DeepSource is an automated code review tool that manages the end-to-end code scanning process and automatically makes pull requests with fixes whenever new commits are pushed or new pull requests.

[Setting up DeepSource](https://deepsource.io/docs/analyzer/go.html) for Go is extremely easy. As soon as you have it set up, an initial scan will be performed on your entire codebase, find scope for improvements, fix them, and open pull requests for those changes.

`go build`