← Tech Asteroid

Golang

December 21st, 2016 by Virtual-Machine

In 2007, Google announced the creation of a new programming language called Go. Go (aka Golang = Go Language) is now rapidly becoming popular among web developers looking for a server side language that offers concurrency, type safety, compiled performance, and is easy to learn and be productive with. For the most part, Go is all these things and more. Much like Java was a simplified variant of C++, Golang is like modern C without some of the more confusing aspects. It is a language that has been purposely built to have a simple explicit syntax, that can be adopted quickly into a team environment and makes working in larger projects more efficient and enjoyable. Despite this simple syntax, there are a few things that either confused or impeded me the first few times I played with Go. This post will serve to highlight some of the things I found initially confusing about Go, and where possible, ways I got around it.

My first stumbling block with Go came pretty early in the process. Go requires an environment variable called GOPATH being set to a directory on your system. This variable is then used by Go when searching for go source dependancies on your system. Any time you use the “go get” command to download dependancies, Go will download them into the GOPATH directory to ensure all your code can find them. To be honest, I still do not like this convention too much and apparently I am not alone. Dave Cheney is a core member of the Go Programming team at Google and he recently made a blog post in which he was critical of this aspect of Go. (Dave Cheney on GOPATH). His points cover very well how I feel about the GOPATH since day one.

Personally, I don't like that my Go code has to live in one directory on my system or I am faced with having to fight the build system. This is breaking my convention on how I deal with projects in every other language that I use. I would much prefer to host my dependancies in the same directory of the project, even when this ends up causing multiple dependancy duplicates across projects. Further, I like the choice of putting my projects in different directories on my system based on the type of project, its clients, and its status. It is my simple way of organizing the projects that I am most active in, and keeping my active project folder clean and tidy. While I am able to do this with Go projects, I definitely fight the build system to resolve the dependancies, and keep my installed binaries in sync with the latest builds. I like the idea Dave posits to allow a project to resolve its dependancies within its own project folder automatically. Unfortunately this is a problem not easily solved without changing the core way Go resolves dependancies.

I follow Dave quite a bit because I truly appreciate his honesty in regards to Go despite being a core member of the team. His thought-experiment for a potential Go 2.0 is very provoking (Dave Cheney on Go 2.0 Thought Experiment). Personally I totally find the apparent duplication of make and new in the language to be an unnecessary redundancy. Go was designed to be as simple as possible, and having two keywords with such similar behaviour goes completely against this philosophy. The more ways there are to do the same thing in a language does not make the language any easier to write, but will make the language harder to read.

Reading is much more important than writing and this can be proved by a simple thought experiment of my own. If you have a team of 10 developers, and each one writes 10% of the final code of the project, that means each one is going to have to read the other 90% of the code that is written by someone else on the team. If the language has many ways to do the same thing, each developer could choose a different way to implement their 10%. What this translates to however is that each developer will have to be able to read the other 90% of the code using conventions that they perhaps never even use and, likely as a result, do not fully understand. If the language has only one way to accomplish something, then everyone's code will look alike and be readily understandable by everyone on the team. Knowing the minor differences between new and make is an impedance to a team's understanding.

Another initial pain point I had with Go was the at times over-bearing nature of the compiler. Full disclosure, I love compiler errors, I'd rather solve a thousand compile time errors and have faith in my final executable than allow even one runtime error. But honestly, the Go compiler can be a huge pain in the rear. What I am specifically referring to is that the Go compiler will issue an error if you import a dependancy or create a variable and never use it. Yes it can be useful to be notified of this as it could be a source of typo or other bug but why this is not a warning rather than an error I may never know. Where this becomes painful is when you are simply coding away and you get to a point you want to test your code. You go to compile your current source code to see what you get and instead you get a compiler error because although your implementation is good enough to get some information, you declared a variable or dependancy that you just haven't yet used. Guess what? You have to either comment out that variable/dependancy or replace the identifier with an underscore if you want your code to compile.

Luckily, Brad Fitzgerald, another core member of the Go team, has built a tool to help with half of this issue. (Goimports By Brad Fitzgerald) is a drop in replacement for gofmt. Gofmt is a tool to automatically format your code. What goimports does on top of the traditional gofmt functionality is adjust your import statements such that your code will pickup dependancies as you use them and remove dependancies as you remove them from references in your source code. If you set this up to run on save, you will thank yourself many times over. This still leaves the issue of unused declared variables but it is one less thing to worry about.

Finally, one last thing I initially was confused by was Go's lack of object orientation. This is definitely not an issue with Go, so much as it was a misconception in my thinking based on past exposure to object oriented languages. I found Go's use of slices to be kind of confusing and the syntax of builtins to be weird. (Official Golang Blog on Slices). A great example of the syntax I found initially confusing was the built in append function.

Append Syntax
slice = append(slice, item)

// append function signature
func append(slice []Type, elems ...Type) []Type

Upon further inspection and reading, it began to make sense to me. The reason I could not call an append method on the slice itself ala : slice.append(item) like I was used to in a language like Ruby or Python was because Go is not object orientated. Slice is not a descendant of object or collection, and it doesn't have inherited methods for appending. Instead many things inside Go can be thought of as typed pointers to value references. Therefore rather than inefficiently put the same behaviour on each runtime object, Go uses generic builtin functions to perform these operations. The additional benefit is that we can say that this builtin does not mutate its inputs, but can be used to overwrite the original slice if desired.

Copy vs Overwrite Append
slice := make([]int, 3)
slice[0] = 1
slice[1] = 2
slice[2] = 3

// To duplicate the original slice and then append to copy
sliceCopy := append(slice, 4)

// To modify the original slice
slice = append(slice, 4)

While I had some initial roadblocks with Go, I can easily state that for the most part it was a positive and enjoyable experience learning the language. No language or ecosystem for that matter is perfect, and Golang despite being almost 10 years old, is still relatively young and maturing. With time we may see that my issues with the language are completely solved by a new tool or feature. But in the meantime, Go is still a powerful and productive language, and will continue to be my server-side workhorse for the foreseeable future.