Common anti-patterns in Go

unnecessary assignment to the blank identifier

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.

unnecessary assignment to the blank identifier

Image source: 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.

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

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 .

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:

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.

‍ can be rewritten as:

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.

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.

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

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:

Good pattern:

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 .

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 :

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 .

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.

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

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:

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.

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

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

More from DeepSource

Get started with deepsource.

DeepSource is free forever for small teams and open-source projects. Start analyzing your code in less than 2 minutes.

Read product updates, company announcements, how we build DeepSource, what we think about good code, and more.

unnecessary assignment to the blank identifier

  • Data Types in Go
  • Go Keywords
  • Go Control Flow
  • Go Functions
  • GoLang Structures
  • GoLang Arrays
  • GoLang Strings
  • GoLang Pointers
  • GoLang Interface
  • GoLang Concurrency

What is Blank Identifier(underscore) in Golang?

_ (underscore) in Golang is known as the Blank Identifier. Identifiers are the user-defined name of the program components used for the identification purpose. Golang has a special feature to define and use the unused variable using Blank Identifier. Unused variables are those variables that are defined by the user throughout the program but he/she never makes use of these variables. These variables make the program almost unreadable. As you know, Golang is a more concise and readable programming language so it doesn’t allow the programmer to define an unused variable if you do such, then the compiler will throw an error. 

The real use of Blank Identifier comes when a function returns multiple values, but we need only a few values and want to discard some values. Basically, it tells the compiler that this variable is not needed and ignored it without any error. It hides the variable’s values and makes the program readable. So whenever you will assign a value to Blank Identifier it becomes unusable.

Example 1: In the below program, the function mul_div is returning two values and we are storing both the values in mul and div identifier. But in the whole program, we are using only one variable i.e. mul . So compiler will throw an error div declared and not used  

Output:  

Example 2: Let’s make use of the Blank identifier to correct the above program. In place of div identifier just use the _ (underscore). It allows the compiler to ignore declared and not used error for that particular variable.

Important Points:  

  • You can use multiple Blank Identifiers in the same program. So you can say a Golang program can have multiple variables using the same identifier name which is the blank identifier.
  • There are many cases that arise the requirement of assignment of values just to complete the syntax even knowing that the values will not be going to be used in the program anywhere. Like a function returning multiple values. Mostly blank identifier is used in such cases.
  • You can use any value of any type with the Blank Identifier.

author

Similar Reads

  • Go Language

Improve your Coding Skills with Practice

 alt=

What kind of Experience do you want to share?

IMAGES

  1. simple: flag unnecessary assignment to the blank identifier · Issue

    unnecessary assignment to the blank identifier

  2. GO 代码规范-腾讯云开发者社区-腾讯云

    unnecessary assignment to the blank identifier

  3. Workflow state action

    unnecessary assignment to the blank identifier

  4. Go Syntax

    unnecessary assignment to the blank identifier

  5. blank identifier golang

    unnecessary assignment to the blank identifier

  6. blank identifier golang

    unnecessary assignment to the blank identifier

VIDEO

  1. Basic Game assignment

  2. Pill identifier Application video

  3. 🔥Delete Blank & Empty Rows in Excel with one Click🔥-Game Changer!#shorts #rajnikanth #powerexcel

  4. Part- 6 : Golang Step by Step

  5. 3 Basic Programming Constructs: Sequence, Selection, & Iteration

  6. if Statements