Logo
Logo

Atharva Pandey/Chapter 1: Foundations of Go Programming

Created Sun, 03 Mar 2024 18:30:00 +0000 Modified Sun, 03 Mar 2024 18:30:00 +0000

Welcome to the exciting world of Go! This chapter is designed to kickstart your journey into Go programming, covering the essentials from “Hello, World!” to functions. Along the way, we’ll explore best practices and highlight some anti-patterns to avoid. Let’s dive in!

Hello World

Every programmer’s journey begins with the quintessential “Hello, World!” It’s a simple yet profound way to get acquainted with any new programming language.\

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}


Best Practice: Always start your Go file with the package declaration, and for executable programs, use package main.

Anti-pattern: Avoid ignoring the conventional structure of a Go program. Neglecting the main package and main function will result in a program that can’t be executed.

Values

Go supports various value types including strings, integers, floats, and booleans. Understanding these basic types is crucial for manipulating data in your Go programs.\

package main

import "fmt"

func main() {
    fmt.Println("Go" + "Lang")  // String concatenation
    fmt.Println("1+1 =", 1+1)    // Integer addition
    fmt.Println("7.0/3.0 =", 7.0/3.0)  // Floating point division
    fmt.Println(true && false)  // Boolean AND
    fmt.Println(true || false)  // Boolean OR
    fmt.Println(!true)          // Boolean NOT
}

Best Practice: Use explicit type declarations for better code readability and to avoid unexpected type inference.

Anti-pattern: Mixing types without explicit conversion can lead to runtime errors. Always perform type conversions where necessary.

Variables

Variables are used to store information that can be referenced and manipulated in a program. Go requires explicit declarations of variables, enhancing type safety and readability.
\

package main

import "fmt"

func main() {
    var a = "initial"  // Type inferred as string
    fmt.Println(a)

    var b, c int = 1, 2  // Declaring multiple variables
    fmt.Println(b, c)

    var d = true  // Boolean variable
    fmt.Println(d)

    var e int  // Zero-value concept: e is initialized to 0
    fmt.Println(e)

    f := "apple"  // Short declaration
    fmt.Println(f)
}


Best Practice: Utilize short variable declarations (:=) for cleaner code and to leverage type inference within local scopes.

Anti-pattern: Overusing global variables or reusing variables for different types within the same scope can lead to code that is difficult to understand and maintain.

Constants

Constants are immutable values that are known at compile time. Use constants to improve code readability and to enforce unchangeable values.

package main

import "fmt"

const Pi = 3.14

func main() {
    const World = "世界"
    fmt.Println("Hello", World)
    fmt.Println("Happy", Pi, "Day")

    const Truth = true
    fmt.Println("Go rules?", Truth)
}

Best Practice: Name constants with meaningful identifiers and use them for values that do not change throughout the program, such as configuration values or mathematical constants.

Anti-pattern: Avoid using constants for values that may change or for values that are used only once; this can lead to unnecessary code rigidity and complexity.

For

The for loop is the only looping construct in Go. It’s flexible and can be used in various ways, from traditional for loops to while-like loops.
\

package main

import "fmt"

func main() {
    // Traditional for loop
    for i := 0; i <= 5; i++ {
        fmt.Println(i)
    }

    // While-like for loop
    j := 0
    for j < 5 {
        fmt.Println(j)
        j++
    }

    // Infinite loop with break
    for {
        fmt.Println("loop")
        break
    }
}

Best Practice: Use the simplest form of the for loop for your use case. Utilize break and continue to control loop execution flow effectively.

Anti-pattern: Avoid complex or nested loops when a simpler loop or alternative logic can achieve the same result, as it can lead to less readable and less maintainable code.

If/Else

Conditional statements in Go are straightforward. The if statement can include an initialization statement, and parentheses are not required around conditions.
\

package main

import "fmt"

func main() {
    if num := 9; num < 0 {
        fmt.Println(num, "is negative")
    } else if num < 10 {
        fmt.Println(num, "has 1 digit")
    } else {
        fmt.Println(num, "has multiple digits")
    }
}

Best Practice: Leverage the initialization statement in if to limit the scope of variables and improve code readability.

Anti-pattern: Avoid deeply nested if statements. Consider using switch statements or refactoring into separate functions for clarity.

Switch

The switch statement in Go simplifies complex conditional blocks. It’s more readable than multiple if/else blocks and supports various types and conditions.
\

package main

import (
    "fmt"
    "time"
)

func main() {
    switch time.Now().Weekday() {
    case time.Saturday, time.Sunday:
        fmt.Println("It's the weekend")
    default:
        fmt.Println("It's a weekday")
    }

    // Switch without an expression is an alternate way to express if/else logic
    t := time.Now()
    switch {
    case t.Hour() < 12:
        fmt.Println("It's before noon")
    default:
        fmt.Println("It's after noon")
    }
}


Best Practice: Use switch for clear, concise conditional logic, especially when comparing a single variable against multiple values.

Anti-pattern: Avoid using switch when a simple if would suffice, or when testing complex conditions that would reduce the readability of the switch statement.

Arrays

Arrays in Go are fixed-size, ordered collections of elements of the same type. They provide a way to group related data together.
\

package main

import "fmt"

func main() {
    var a [5]int  // Declare an array of 5 integers
    fmt.Println("emp:", a)

    a[4] = 100  // Set a value
    fmt.Println("set:", a)
    fmt.Println("get:", a[4])

    fmt.Println("len:", len(a))  // Get the length of the array

    b := [5]int{1, 2, 3, 4, 5}  // Declare and initialize an array
    fmt.Println("dcl:", b)
}

Best Practice: Use arrays when you need a fixed-size collection of elements. Consider using slices (dynamic arrays) for more flexibility.

Anti-pattern: Avoid using arrays when the size of the collection might change, as arrays have a fixed size and cannot be resized.

Slices

Slices are a key data type in Go, providing a more powerful and flexible interface to sequences of data than arrays.\

package main

import "fmt"

func main() {
    s := make([]string, 3)  // Create a slice of strings with a length of 3
    fmt.Println("emp:", s)

    s[0] = "a"
    s[1] = "b"
    s[2] = "c"  // Assign values to slice elements
    fmt.Println("set:", s)
    fmt.Println("get:", s[2])

    s = append(s, "d")  // Append values to the slice
    s = append(s, "e", "f")
    fmt.Println("apd:", s)

    c := make([]string, len(s))
    copy(c, s)  // Copy a slice
    fmt.Println("cpy:", c)

    l := s[2:5]  // Slice a slice
    fmt.Println("sl1:", l)
}

Best Practice: Utilize slices for most collections of data. They are more versatile than arrays and can grow as needed.

Anti-pattern: Avoid using arrays when a slice would provide the necessary flexibility. Also, be mindful of slice capacity and length to avoid unnecessary memory allocations.

Maps

Maps are Go’s built-in associative data type (sometimes called hashes or dicts in other languages).\

package main

import "fmt"

func main() {
    m := make(map[string]int)  // Create a map with string keys and int values

    m["k1"] = 7
    m["k2"] = 13  // Set key/value pairs in the map

    fmt.Println("map:", m)

    v1 := m["k1"]
    fmt.Println("v1: ", v1)

    fmt.Println("len:", len(m))  // Get the number of key/value pairs in the map

    delete(m, "k2")  // Delete a key/value pair
    fmt.Println("map:", m)

    _, prs := m["k2"]  // The optional second return value indicates if the key was present in the map
    fmt.Println("prs:", prs)
}


Best Practice: Use maps for dynamic, unordered collections of elements, where fast lookups, additions, and deletions are important.

Anti-pattern: Avoid using maps where a slice or array would suffice, particularly if the order of elements is important or if you’re working with a fixed set of elements.

Range

The range keyword in Go is used to iterate over elements in a variety of data structures. It’s particularly useful with slices and maps.\

package main

import "fmt"

func main() {
    nums := []int{2, 3, 4}
    sum := 0
    for _, num := range nums {
        sum += num  // Sum the numbers in a slice
    }
    fmt.Println("sum:", sum)

    for i, num := range nums {
        if num == 3 {
            fmt.Println("index:", i)  // Find the index of 3 in the slice
        }
    }

    kvs := map[string]string{"a": "apple", "b": "banana"}
    for k, v := range kvs {
        fmt.Printf("%s -> %s\n", k, v)  // Iterate over key/value pairs in a map
    }

    for i, c := range "go" {
        fmt.Println(i, c)  // Iterate over characters in a string
    }
}

Best Practice: Leverage range for concise and safe iteration over slices, maps, and strings. Use the blank identifier _ to ignore index or value if not needed.

Anti-pattern: Avoid unnecessary use of range where a simple loop would suffice, or when you need more control over the iteration process.

Functions

Functions in Go are central to the language and can be defined in various ways, including with multiple return values and variadic parameters.\

package main

import "fmt"

// Basic function with two int parameters and an int return value
func add(x int, y int) int {
    return x + y
}

// Function with multiple return values
func vals() (int, int) {
    return 7, 3
}

// Variadic function that accepts an arbitrary number of ints
func sum(nums ...int) {
    total := 0
    for _, num := range nums {
        total += num
    }
    fmt.Println("total:", total)
}

func main() {
    res := add(1, 2)
    fmt.Println("1+2 =", res)

    a, b := vals()
    fmt.Println("Vals:", a, b)

    sum(1, 2)
    sum(1, 2, 3)

    nums := []int{1, 2, 3, 4}
    sum(nums...)  // Pass a slice of ints to a variadic function
}

Best Practice: Use functions to encapsulate reusable code blocks. Leverage multiple return values and variadic functions for more flexible and expressive code.

Anti-pattern: Avoid overly complex functions that do too much. Aim for single-responsibility functions that are easier to test, maintain, and understand.


This chapter has laid the groundwork for your Go programming journey, covering fundamental concepts and constructs. Remember to practice writing Go code that adheres to the language’s idiomatic patterns and best practices. Happy coding!