Functions in Go

Bashayr Alabdullah
6 min readJul 23, 2021

--

Clean Code Series.

In general, we know as developers how much the functions are useful. I’m not going to list the advantages of writing function or the situation needed in your code to implement function… The following are some helpful tips and definitions that are meant to write amazing functions 😎

Note** this post is the first series of Go: Clean Code.

How to write functions? Golang isn’t different from other languages rules and philosophy. So, here some points that we should consider when implement function.

  • Make it small: before years I was given a handover project from developer who wrote about 7000 lines of ugly code. His code was very complicated, I’ve been take two weeks thinking how to refactor this code. The code was only integration between two systems, just requests/response functions with some logic. He wrote one function that handles many logic and it was impossible to anyone imagine or guess the goal of this function from first look. It’s important to make the functions independent from each other as possible and must do one job and only one job well. So please 😢! if you find yourselves writing functions that do multiple things, you might consider replacing them by multiple functions instead!
  • Make a story: I prefer to make my app structured as much as possible, but here in Go structuring the app is different, check here if you interest on how do you structure your Go app. Structuring function is may part of structuring app. Let’s say I have to write function to calculate total invoice and send it to the customer. First, I need to write a GET handler that contains three parts: validate request, calculate invoices and send to customer. Each of these three parts, will be in separate function and should lead you to the next function in a compelling order like told
    a story.
  • Choose a good name if it a declaration function: there are two types of functions in Go, declarations and anonymous. A function that has a name is called `function declaration` and the opposite which can be defined inline without the need for a name called `anonymous function`. I discuss these types in more details below, so now I want to just talk about choosing name 😬. Naming conventions for Go functions should be:
    - begin with a letter, and can have any number of additional letters and numbers.
    - better to make name verb or verb phrase names.
    - they have to be consistent in order for you to pick the correct function without any additional exploration.
    - use descriptive names.
    - use camelCase 🐪 🐫, one of my pull request I wrote names of function like this:
func get_users() {//something between...}

when my colleague reviewed my code he said: Bashayr this is not a Python code, it’s Go 😅. In Go, the convention is to use MixedCaps or mixedCaps rather than underscores to write multiword names. Moreover, the names in Go effect that decides on the visibility of identifiers outside a package for example, if the functions with names that start with an uppercase letter will be exported to other packages.

Declaring and Calling Functions

Function declarations

As I mentioned earlier, a function declaration has a name, a list of parameters, an optional list of results, and a body. In Go function is defined using the func keyword.

func name(parameterlist) (resultlist) {
//body
}

In square function example1.go, x and y are parameters in the declaration, 315 and 6 are arguments of the call, and the function returns an int value.

A function that has a result list must end with a return statement.

The function with a sequence of parameters or results of the same type, the type can be written only once. These two declarations are equivalent:

Function parameters

The input parameters are optional for a function. A function can be declared without any parameters. The initial values of parameters set to the arguments supplied by the caller; I came from Python mindset, which I was write many functions with parameters contains default parameter values, but in Go there is no concept of default parameter values 😢 . Go supports two different ways to pass arguments to the function pass by value and pass by reference. By default, the arguments in Go are passed by value, so uses the call by value way to pass the arguments to the function.

pass by value the function receives a copy of each argument; modifications
to the copy do not affect the caller. Consider the function swap()definition with its call as follows:

the result shows that there is no change in the values though they had been changed inside the function:

a = 100
b = 200
a = 100
b = 200

pass by reference we are passing the address of the variables, so the function copies the address of an argument. The caller may be affected by any modifications inside the function. To use this type, you need to declare the function parameters as pointer types * and address operator & is used to get the address of a variable:

result:

a = 100
b = 200
a = 200
b = 100

Variadic functions

In Go you can pass zero or more arguments in the variadic function. To declare a variadic function, the type of the last parameter is preceded by an ellipsis, i.e, (), which indicates that the function may be called with any number of arguments of this type.

result:

0
3
10

Variadic parameter is often used for string formatting and can make your code more readable. I like to use it if the number of input parameter are unknown or some are optional parameters.

Return multiple values

Go functions can return multiple distinct values, which saves you from having to create a dedicated structure in order to be able to receive multiple values at once from a function. It’s better to avoid writing function with multiple values return, it’ll make harder to read and understand the output of function.

result:

20000 result

Multiple values are often used to return an error value along with the result or a boolean to indicate success, for example:

you can call this function in two ways:

links, err := getLinks(url)
To ignore one of the values, assign it to the blank identifier :
links, _ := getLinks(url) // errors ignored

The names are particularly valuable when a function returns multiple results of the same type. In a function with named results, the operands of a return statement may be omitted. This is called a bare return , it can reduce code duplication, but they rarely make code easier to understand.

Recursion

It’s the same concept applies in programming languages as well. If a program allows to call a function inside the same function, then it is called a recursive function call. It makes your code with small implementation.

Anonymous functions

It’s written like function declaration with func keyword but without a name. It’s usually considered a good practice for anonymous functions to have a small implementation and avoid write too many anonymous functions in your app.

func (parameterlist) (resultlist) {
//body
}()

Deferred functions

Deferred function is called with prefix by the keyword defer. Defer pushes a function call to a stack which is executed in Last In First Out (LIFO) order after the return of the surrounding function. It used with paired operations like open and close, connect and disconnect i.e, (closing the file connection, database…etc). It ensures that these cleanups are performed before the function exits.

func ReadFile(filename string) ([]byte, error) {                       f, err := os.Open(filename)                       
if err != nil {
return nil, err
}
defer f.Close()
return ReadAll(f)
}

Deferred functions aren’t executed until the very end of a function’s execution. Hence, if you have defer statement in a loop as here:

for _, filename := range filenames {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
// body..
}

it could run out of file descriptors since no file will be closed until all file have been processed. To solve this issue you have to create another function that contains loop body and the defer statement:

for _, filename := range filenames {
if err := doFile(filename); err != nil {
return err
}
}
func doFile(filename string) error {
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
// body..
}

Errors

Some functions need to return errors such as function contains network call, database connection…etc.

I’ll write about error handling in more details next post.

I hope you find this post is helpful, bye!

References:

  • Most examples from: The Go Programming Language Book by Alan A. A. Donovan and Brian Kernighan.
  • Clean Code Book by Robert Cecil.

--

--

Bashayr Alabdullah

Salam, I'm Tech Engineer 🚀. I blend technology insights, inspiration, and fun in my feed. #innovation #discovery 🥑🎶🎯🌻 *my personal -own- thoughts*