Mastering the Art of Reading Go code

* I could not come up with any good name for this post, so here it is. It is a case study for reading go code.

I have worked with Java/Python for more than 9 years now. Most of my production code was in Java. When I want to write a code to parse some text, first I think of defining a parser interface that has a parse() method, then creating appraiser class that implements the interface, then constructing an instance of the parser object, then injecting the parser to the code that I need it and then giving the text to the parser and finally getting back the result. It’s a long path but my brain has been used to it. Despite that I have almost zero idea how this is done in a good procedural code. In my go learning path I want to see how a good procedural code looks like to be able to write clean code in that manner. Most people (including me) believe that in order to be a good writer you need to read a lot. So that is what this post will be. Reading Go code and learning stuff from it.I’m going look at a small go code by Robert Griesemer one of original go authors.

“The ugly fact is books are made out of books.”

Cormac McCarthy

Unlike Java, Go does not support exceptions and handles errors with defer/panic/recovers. Read more about it in Go blog. However they recommend panic only for situations where app cannot continue its flow and the usual way for handling errors is to return them as the second output of a method. Almost after every function call there is an if which takes care of any error that could occur.

res,err:= f()
if err != nil {
  handle(err)
}

I have no comment it’s a good thing or not. In my opinion it makes the code not to look clear having lots of ifs. However the idea of handling errors after every call is good thing. Assume a case where you want to call a function that might return an error. You can do a block like this.

if err := f(); err != nil {
    return err
}

tryhard detects these cases and can rewriting them with a try(). Now let’s get into reading tryhard code. Code isn’t big and the package is flat (all files in one directory). Author has broke code in three files in package main, so everything in each file is accessible from another file.

main.go: Iterates over input files in OS args, tryhards them (detects try candidates) and reports the statistics in stdout.
try.go: Implements the logic of detecting try candidates.
stats.go: Computes statistics about candidates.

flag library is used for parsing flags in the program run arguments. To use flag you just need to define flag variables and then call flag.Parse(). No need to extend some class, customizing it and creating an object of it! You can do basically everything by calling methods ion flag library. You can iterate over flags with flag.Arg(i).To change the way flag usages are shown you can pass your usage func to.

var (
    list    = flag.Bool("l", false, "list positions of ...")
    rewrite = flag.Bool("r", false, "rewrite potential ...")
)

func usage() {
    fmt.Fprintf(os.Stderr, "usage: tryhard [flags] [path ...]\n")
    flag.PrintDefaults()
}

func main() {
    flag.Usage = usage
    flag.Parse()

Then main.go reads and parses each given file with go builtin parser. An interesting fact about go is that go compiler (gc) is written in go! To tryhard each file, main.go just passes the files it read to tryFile() in try.go. One of the nice things about this code that I like is that you can barely see a function with two or more parameters, most functions are nullary or unary. After finding a try candidate count() is called with the type of the candidate. count() is in stats.go and keeps the count of candidates in a global variable. Using global variables reduces the number of parameters passed significantly. I remember that in all programming classes I had, they used to compare global variables to hell. However, in fact, global state variables are not hell. Non-linearizable shared variables or global state variables are usually hell. Finally, reportCount in stats.go is called which outputs the statistics.

After reading the code, I absolutely liked it. I appreciate the way the author shortened names, like NArg for Number of Args. Go comments are not formatted like JavaDoc, but they are much more readable, as they are written for coders. Another nice aspect is that you can easily follow the code’s flow, as there are no injections or annotations. I plan to read larger Go codebases next time.