Style and Error Handling

There are two quite major points about Go that take some getting used to when ramping up on learning the language: style and error handling. We’ll first talk about what is considered “idiomatic” style in the Go community.

Style

Gofmt

Go comes with a tool that formats Go programs called “gofmt”. Gofmt formats your program, and is prominent in the Go community. It would not be a stretch to say that every popular (let’s say > 500 stars on GitHub) open source library uses it. When using gofmt, you are not allowed to add exceptions like you can with tools such as PEP8 for Python. Your lines can be longer than 80 chars without warning. You can of course split your long lines up, and gofmt will format them accordingly. You cannot tell gofmt to use spaces instead of tabs.

You might find such strict formatting (maybe you hate tabs) backwards and annoying, but gofmt likely played a big role in Go’s success. Since everyone’s code looks similar, it takes an element of surprise out of looking at others’ code when debugging or trying to understand it. This made it easier to contribute to the standard library and open source libraries, in turn speeding up the growth of the Go community.

To show a very simple example of gofmt in action, here is some code that hasn’t been run through gofmt:

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main(){
 6 a       := "foo"
 7 someVar := "bar"
 8 
 9 fmt.Println(a, someVar)
10 }

This is a valid program and it will run, but it should be run through gofmt to look like this:

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6     a := "foo"
 7     someVar := "bar"
 8 
 9     fmt.Println(a, someVar)
10 }

While you may not agree with all of the rules of gofmt, it is so widely used within the community that it has become a requirement. You should always gofmt your Go code.

Many editors have integrations that allow you to run gofmt on save. We recommend running goimports on save. Goimports, according to its godoc, “updates your Go import lines, adding missing ones and removing unreferenced ones. In addition to fixing imports, goimports also formats your code in the same style as gofmt so it can be used as a replacement for your editor’s gofmt-on-save hook.”2

Also, as a bonus, use gofmt -s to automatically simplify some of your code:

 1 package main
 2 
 3 type Animal struct {
 4     Name    string
 5     Species string
 6 }
 7 
 8 func main() {
 9     animals := []Animal{
10         Animal{"Henry", "cat"},
11         Animal{"Charles", "dog"},
12     }   
13 }

After gofmt -s, this becomes:

 1 package main
 2 
 3 type Animal struct {
 4 	Name    string
 5 	Species string
 6 }
 7 
 8 func main() {
 9 	animals := []Animal{
10 		{"Henry", "cat"},
11 		{"Charles", "dog"},
12 	}
13 }

Note how the extra Animal struct names are unnecessary in the slice []Animal, and are therefore removed. gofmt -s makes stylistic changes and does not affect the logic of the code at all, meaning it is safe to run gofmt -s all the time.

Short Variable Names

Another contentious topic in the earlier days of Go was the use of short variable names. If you look at the Go source code, you’ll see a lot of code that looks like this:

 1 func Unmarshal(data []byte, v interface{}) error {
 2     // Check for well-formedness.
 3     // Avoids filling out half a data structure
 4     // before discovering a JSON syntax error.
 5     var d decodeState
 6     err := checkValid(data, &d.scan)
 7     if err != nil {
 8         return err
 9     }
10 
11     d.init(data)
12     return d.unmarshal(v)
13 }

You might be thinking, “why use such a short and useless variable name like d? It doesn’t tell me anything about what the variable is holding.” It’s a fair point, especially considering that for years we’ve been told that having descriptive variable names is very important. But the authors of Go had something else in mind, and many people have come to embrace short variable names. From a page containing advice on reviewing Go code:

“Variable names in Go should be short rather than long. This is especially true for local variables with limited scope. Prefer c to lineCount. Prefer i to sliceIndex.”

Shorter variable names make control flow easier to follow, and allow the reader of the code to focus on the important logic, such as function calls. A general rule of thumb is, if a variable spans less than 10 lines, use a single character. If it spans more, use a descriptive name. At the same time, try to minimize variable span, and functions shorter than 15 lines. Most of the time, this produces readable, idiomatic Go code.

Golint

golint is a linter for Go, and it differs from gofmt in that it prints style mistakes, whereas gofmt reformats your code. To install golint, run:

go get -u github.com/golang/lint/golint

As with gofmt, you can’t tell golint to ignore certain errors. However, golint is not meant to be used as a standard, and will sometimes have false positives and false negatives. On Go Report Card we’ve noticed a lot of repositories with golint errors like the following:

Line 29: warning: exported type Entry should have comment or be unexported (golint)

This is just suggesting that an exported type should have a comment, otherwise it should be unexported. This is nice for godoc, which displays the type’s comment right below it. You might also see a warning like this:

Line 5: warning: if block ends with a return statement, so drop this else and outdent its block (golint)

Here’s a piece of code where that warning would show up when running golint:

1 func truncate(s, suf string, l int) string {
2     if len(s) < l {
3         return s
4     } else {
5         return s[:l] + suf
6     }
7 }

What golint is saying here is that because we return on line 3, there’s no need for the else on the following line. Thus our code can become:

1 func truncate(s, suf string, l int) string {
2     if len(s) < l {
3         return s
4     }
5     return s[:l] + suf
6 }

Which is a bit smaller and easier to read.

That’s all we’re going to cover on golint - we do suggest using it because it can show you ways to make your code simpler as well as more suitable for godoc. There’s no need to fix all of its warnings though, if you think it’s too noisy.

Error Handling

Error handling may take some getting used to when learning Go. In Go, your functions will normally return whatever values you want to return, as well as an optional error value. To give a simple example:

 1 // lineCount returns the number of lines in a given file
 2 func lineCount(filepath string) (int, error) {
 3 	out, err := exec.Command("wc", "-l", filepath).Output()
 4 	if err != nil {
 5 		return 0, err
 6 	}
 7 	// wc output is like: 999 filename.go
 8 	count, err := strconv.Atoi(strings.Fields(string(out))[0])
 9 	if err != nil {
10 		return 0, err
11 	}
12 
13 	return count, nil
14 }

Note the multiple if err != nil checks. These are very common in idiomatic Go code, and sometimes people who are new to Go have trouble adjusting to having to write them all the time. You may see it as unnecessary code duplication. Why not have try/except like other languages?

We had similar thoughts when first starting out with Go, but warmed up to the error checking. When you’re that strict about returning and checking errors, it’s hard to miss where and why an error is happening.

We could even go ahead and make those errors more specific:

 1 // lineCount returns the number of lines in a given file
 2 func lineCount(filepath string) (int, error) {
 3 	out, err := exec.Command("wc", "-l", filepath).Output()
 4 	if err != nil {
 5 		return 0, fmt.Errorf("could not run wc -l: %s", err)
 6 	}
 7 	// wc output is like: 999 filename.go
 8 	count, err := strconv.Atoi(strings.Fields(string(out))[0])
 9 	if err != nil {
10 		return 0, fmt.Errorf("could not convert wc -l output to integer: %s", err)
11 	}
12 
13 	return count, nil
14 }

Just make sure not to capitalize the error string unless beginning with proper nouns or acronyms, because the error will be logged in the caller with something like this:

1 filepath := "/home/gopher/somefile.txt"
2 lines, err := lineCount(filepath)
3 if err != nil {
4     log.Printf("ERROR: lineCount(%q): %s", filepath, err)
5 }

and the error line will flow better without a capital letter appearing in the middle of the log line:

2017/09/21 03:57:55 ERROR: lineCount("/home/gopher/somefile.txt"): Could not run wc -l

vs.

2017/09/21 03:57:55 ERROR: lineCount("/home/gopher/somefile.txt"): could not run wc -l

Wrapping Up

Compared to most languages, Go is very opinionated about proper style. It can take getting used to, but the advantage is that Go projects all follow the same style. This reduces mental overhead and lets you focus on the logic of the program. For more examples of idiomatic Go code, we recommend reading the Go source code itself. One way to do this is to browse Go standard library’s godoc documentation. Clicking on a function name on godoc.org will take you to a page which displays the source containing that function. Don’t be afraid to read the source - it is very approachable and, partly due to it being run through gofmt, very readable.