Book Image

Go Design Patterns

By : Mario Castro Contreras
Book Image

Go Design Patterns

By: Mario Castro Contreras

Overview of this book

Go is a multi-paradigm programming language that has built-in facilities to create concurrent applications. Design patterns allow developers to efficiently address common problems faced during developing applications. Go Design Patterns will provide readers with a reference point to software design patterns and CSP concurrency design patterns to help them build applications in a more idiomatic, robust, and convenient way in Go. The book starts with a brief introduction to Go programming essentials and quickly moves on to explain the idea behind the creation of design patterns and how they appeared in the 90’s as a common "language" between developers to solve common tasks in object-oriented programming languages. You will then learn how to apply the 23 Gang of Four (GoF) design patterns in Go and also learn about CSP concurrency patterns, the "killer feature" in Go that has helped Google develop software to maintain thousands of servers. With all of this the book will enable you to understand and apply design patterns in an idiomatic way that will produce concise, readable, and maintainable software.
Table of Contents (17 chapters)
Go Design Patterns
Credits
About the Author
About the Reviewer
www.PacktPub.com
Customer Feedback
Preface

Functions


A function is a small portion of code that surrounds some action you want to perform and returns one or more values (or nothing). They are the main tool for developer to maintain structure, encapsulation, and code readability but also allow an experienced programmer to develop proper unit tests against his or her functions.

Functions can be very simple or incredibly complex. Usually, you'll find that simpler functions are also easier to maintain, test and debug. There is also a very good advice in computer science world that says: A function must do just one thing, but it must do it damn well.

What does a function look like?

A function is a piece of code with its own variables and flow that doesn't affect anything outside of the opening and close brackets but global package or program variables. Functions in Go has the following composition:

func [function_name] (param1 type, param2 type...) (returned type1, returned type2...) { 
    //Function body 
} 

Following the previous definition, we could have the following example:

func hello(message string) error { 
    fmt.Printf("Hello %s\n", message) 
    return nil 
} 

Functions can call other functions. For example, in our previous hello function, we are receiving a message argument of type string and we are calling a different function fmt.Printf("Hello %s\n", message) with our argument as parameter. Functions can also be used as parameters when calling other functions or be returned.

It is very important to choose a good name for your function so that it is very clear what it is about without writing too many comments over it. This can look a bit trivial but choosing a good name is not so easy. A short name must show what the function does and let the reader imagine what error is it handling or if it's doing any kind of logging. Within your function, you want to do everything that a particular behavior need but also to control expected errors and wrapping them properly.

So, to write a function is more than simply throw a couple of lines that does what you need, that's why it is important to write a unit test, make them small and concise.

What is an anonymous function?

An anonymous function is a function without a name. This is useful when you want to return a function from another function that doesn't need a context or when you want to pass a function to a different function. For example, we will create a function that accepts one number and returns a function that accepts a second number that it adds it to the first one. The second function does not have a declarative name (as we have assigned it to a variable) that is why it is said to be anonymous:

func main(){ 
    add := func(m int){ 
         return m+1 
} 
 
    result := add(6) 
 
    //1 + 6 must print 7 
    println(result) 
} 

The add variable points to an anonymous function that adds one to the specified parameter. As you can see, it can be used only for the scope its parent function main and cannot be called from anywhere else.

Anonymous functions are really powerful tools that we will use extensively on design patterns.

Closures

Closures are something very similar to anonymous functions but even more powerful. The key difference between them is that an anonymous function has no context within itself and a closure has. Let's rewrite the previous example to add an arbitrary number instead of one:

func main(){ 
    addN := func(m int){ 
        return func(n int){ 
            return m+n 
        }            
    } 
 
    addFive := addN(5) 
    result := addN(6)  
    //5 + 6 must print 7 
    
    println(result) 
}

The addN variable points to a function that returns another function. But the returned function has the context of the m parameter within it. Every call to addN will create a new function with a fixed m value, so we can have main addN functions, each adding a different value.

This ability of closures is very useful to create libraries or deal with functions with unsupported types.

Creating errors, handling errors and returning errors.

Errors are extensively used in Go, probably thanks to its simplicity. To create an error simply make a call to errors.New(string) with the text you want to create on the error. For example:

err := errors.New("Error example") 

As we have seen before, we can return errors to a function. To handle an error you'll see the following pattern extensively in Go code:

func main(){ 
    err := doesReturnError() 
    if err != nil { 
        panic(err) 
    } 
} 
 
func doesReturnError() error { 
    err := errors.New("this function simply returns an error") 
    return err 
} 

Function with undetermined number of parameters

Functions can be declared as variadic. This means that its number of arguments can vary. What this does is to provide an array to the scope of the function that contains the arguments that the function was called with. This is convenient if you don't want to force the user to provide an array when using this function. For example:

func main() { 
    fmt.Printf("%d\n", sum(1,2,3)) 
    fmt.Printf("%d\n", sum(4,5,6,7,8)) 
} 
 
func sum(args ...int) (result int) { 
    for _, v := range args { 
        result += v 
    } 
    return 
} 

In this example, we have a sum function that will return the sum of all its arguments but take a closer look at the main function where we call sum. As you can see now, first we call sum with three arguments and then with five arguments. For sum functions, it doesn't matter how many arguments you pass as it treats its arguments as an array all in all. So on our sum definition, we simply iterate over the array to add each number to the result integer.

Naming returned types

Have you realized that we have given a name to the returned type? Usually, our declaration would be written as func sum(args int) int but you can also name the variable that you'll use within the function as a return value. Naming the variable in the return type would also zero-value it (in this case, an int will be initialized as zero). At the end, you just need to return the function (without value) and it will take the respective variable from the scope as returned value. This also makes easier to follow the mutation that the returning variable is suffering as well as to ensure that you aren't returning a mutated argument.