Getting Started with Go: 3 Tips for Engineering Interns

Engineering Intern Working

When I received my first code review as a fresh new engineering intern, I was horrified. How could I have received 87 comments on a 50 line code change? After getting A’s in all my CS classes, how could I be so unprepared for programming in the real world? After taking a deeper look at the comments, I realized that my fundamentals were fine, I simply didn’t know any of the industry style guidelines or best practices. The code review comments were extremely helpful in teaching me the industry standards for programming in Go. To help any future engineering interns, this blog post is a list of the three Go styles and best practices I wish I had known before I started my internship.

1. Make Logical Statements Simple and Avoid Nesting Code

One of the most important qualities of good code is readability. In Go (and many other languages), nested statements and confusing logic can make code very difficult to read. While we have to constantly make trade-offs across many different dimensions—like run-time, space, and reusability—readability is key for long-term code maintainability. As Harold Abelson, a Professor of Electrical Engineering and Computer Science at MIT, says “Programs must be written for people to read, and only incidentally for machines to execute.” If your coworkers can’t read your code, they also can’t fix or maintain your code easily in the future. Thus, being able to write clean, readable code is a necessary skill for any engineer. Here’s how to think about writing cleaner logic and removing the nested code from Go. Consider the following block of code:

type Person struct {
    dog *Dog
}

type Dog struct {
    collar *Collar
}

type Collar struct {
    name *String
}

// Checks if the person’s dog’s tag’s
// label is equal to the name provided.
func checkTagLabel(person Person, name string) bool {
    if person.dog != nil {
        if person.dog.collar != nil {
            if person.dog.collar.tag != nil {
                if *person.dog.collar.tag == name {
                    return true
                }
            }
        }
    }
    return false
}

In the example above, the nested if-statements are used to check if the person has a dog, the dog has a collar, the collar has a tag, and the tag equals the name given. The nested if-statements make the code hard to read. A better, more readable, approach to the same problem would be:

func checkTagLabel(person Person, name string) bool {
    if person.dog != nil && person.dog.collar != nil &&
      person.dog.collar.tag != nil &&
      *person.dog.collar.tag == name {
        return true
    }
    return false
}

This approach is much cleaner and more readable, but we can still do better. Boolean logic statements can be very hard to read, so the simpler the statement, the better. A great way to simplify complicated if-statements is to reverse the logic:

func checkTagLabel(person Person, name string) bool {
    if person.dog == nil {
        return false
    }
    if person.dog.collar == nil {
        return false
    }
    if person.dog.collar.tag == nil {
        return false
    }
    return *person.dog.collar.tag == name
}

Now, we have arrived at an approach that solves the same problem as the original example but is far more readable!

2. Errors (fail hard, fail fast)

Living in a society where failure is often shunned, it’s hard to imagine there ever being a time to intentionally fail…but with Go, there is! When it comes to writing maintainable software, there is a time and place to fail hard, fail fast. In some situations, it is actually better to error than to return unexpected results silently. Failing at the right time allows us to debug problems more easily in our code, identify any failures early, and detect issues with our modeling. This can prevent larger issues in the future.

// Logic for code that should only be called on weekdays.
if date.IsWeekday() {
    // Execute code.
} else {
    // Script should have never executed on a weekend.
    fmt.Errorf(“Script called on a non-weekday”)
}

3. Use Fewer Blank Lines in Go

Coming from other programming languages where it is common to have blank lines after blocks of code, it can feel tempting to use unnecessary blank lines when getting started in Go. Due to Go’s error checking pattern, it might seem like having a blank line after each error check would improve readability. However, since many operations in Go utilize error checks, if we chunk after every error check operation, it can hurt code readability because our code would be mostly blank lines.

Consider this implementation for adding two integers stored as a string:

x := “42”
y := “8”

// Hard to Read
valX, err := strconv.Atoi(x)
if err != nil {
    panic(“could not parse x to int”)
}

valY, err := strconv.Atoi(y)
if err != nil {
    panic(“could not parse y to int”)
}

result := valX + valY

Utilizing the blank lines here is less ideal because we have separated one logical chunk into three pieces. A better implementation would be:

// Easier to Read
valX, err := strconv.Atoi(x)
if err != nil {
    panic(“could not parse x to int”)
}
valY, err := strconv.Atoi(y)
if err != nil {
    panic(“could not parse y to int”)
}
result := valX + valY

This implementation groups converting the first string to an integer and checking for errors, converting the second string to an integer and checking for errors, and finally adding the two numbers together. This makes more sense as a chunk since we complete the goal of adding two numbers together.

Blank Lines Shouldn’t Separate Operations and Error Checks

There should never be a blank line between an error check and its preceding operation. Having a blank line between the two could potentially cause other developers, or even yourself, to accidentally add logic between the operation and the error check because they seem conceptually decoupled.

// Bad
valX, err := strconv.Atoi(x)

if err != nil {
    panic(“could not parse x to int”)
}

// Good
valX, err := strconv.Atoi(x)
if err != nil {
    panic(“could not parse x to int”)
}

These are some of the Go best practices that I wish I’d known when I started as an intern, however, there are many more best practices and guidelines for you to check out! The best way to learn them are to get code reviews from coworkers, read through Go’s style guide, and read code open sourced by other companies. So good luck in your engineering internships and when you get back your first pull request with more comments than code remember it’s a normal part of the internship process and that you’re not alone.

Interested in shift engineering roles? Visit https://shift.com/careers

Comments

comments