I've been using the
Go programming language
for almost three months now pretty much full time. I have moved from the
Desktop Experience team working on
Unity, into the
Juju team. One of the main reasons I
moved was to learn Go. It had been too long since I had learned another
language, and I felt it was better to dive in, than to just mess with it on my
own time.
A friend of mine had poked around with Go during a hack fest and
blogged about his
thoughts. This was just before I really started poking around. Interestingly
the main issues that Aldo found frustrating with the errors for unused
variables and unused imports, I have found not to be such a big deal.
Passingly griping, sure, but not a big issue. Having the language enforce
what is often a lint checker in other languages I see as an overall
benefit. Also, even though I don't agree with the Go formatting rules,
enforced by
gofmt, it doesn't matter. It doesn't matter because all code is
formatted by the tool prior to commit. As an emacs user, I found the
go-mode to be
extremely helpful, as I have it formatting all my code using
gofmt before
saving. I never have to think about it. One thing I couldn't handle though,
was the eight character tabs. Luckily emacs can hide this from me.
;; Go bits.
(require 'go-mode-load)
(add-hook 'before-save-hook #'gofmt-before-save)
(add-hook 'go-mode-hook (lambda () (setq tab-width 4)))
There are some nice bits to Go. I very much approve of channels being first
class objects, and the use of channels to communicate between concurrently
executing code. Go routines are also nifty, although I've not used them too
much myself yet. Our codebase does, but I've not poked into all the nooks and
crannies yet.
However there are several things which irritate the crap out of me with Go.
Error handling
The first one I guess is a fundamental design decision which I don't really
agree with. That is around error handling being in your face so you have to
deal with it, as opposed to exceptions, which are all to often not thought
about. Now if our codebase is in any way representative of Go code out there,
this is just flat out wrong. The most repeated lines of code in the codebase
would have to be:
if err != nil {
return nil
}
This isn't error handling. This is just passing it up to the chain, which is
exactly what exception propagation does, only Go makes your codebase two to
three times larger due to needing these three lines after every line of code
that calls into another function. This is one thing I really dislike, but
unlikely to change.
As a user of a language though, there are other things that could be added at
the language level to make things slightly nicer. Syntactic sugar, as it is
often known, makes the code easier to read.
If the language is wanting to keep the explicit handling of errors in the
current way, how about some sugar with that.
Instead of
func magic() (*type, error) {
something, err := somefunc("blah")
if err == nil {
return nil, err
}
otherThing, err := otherfunc("blah")
if err == nil {
return nil, err
}
return foo(something, otherThing), nil
}
we had some magic sugar, say a built-in method like
raise_error, which
interrogated the function signature, and returned zeroed values for all
non-error types, and the error, and returned only non-error values, we could
have this
func magic() (*type, error) {
something := raise_error(somefunc("blah"))
otherThing := raise_error(otherfunc("blah"))
return foo(something, otherThing), nil
}
The range function
There are several different issues I have with the
range function.
- range returns one or two parameters, but the language doesn't allow any user defined
functions to return one or two parameters, range is super special
- using range with a slice or array and getting a single value, doesn't give
you the value, but instead the index - I never want this
- there is no way to define range behaviour for a user defined type
These three things are mostly equal in annoyance factor. I'd love to see this
change.
No generics
Initially I accepted this as a general part of the language. Shouldn't be a
big deal right? C doesn't have generics. I guess I spent too long with C++
then.
My first real annoyance was when I had two integers, and I wanted to find the
maximum value of the two. I go to look in the standard library and find
math.max. However that is just for the
float64 type. The standard response
from the team was "it is only a two line function". My response is "that's not
the point".
Since there is no function overloading, nor generics, there is no way with the
language at this stage to make a standard library function that determines the
maximum value of two or more numeric types, and return that maximum in the
same type as the parameters. Generics would help here.
A second case for generics is standard containers. The primary container in
Go at this stage is the map. So many places in our codebase we have
map[string]interface{}. The problem with this is that you have to
cast all values retrieved from the map. There is also no
set,
multi_map, or
multi_set. Since there is no way to provide
simple iteration for user defined types, you can't easily define your own
set type and have simple iteration using
range.
Interfaces that aren't explicitly marked as being implemented help in some
ways to provide features provided by generic types and functions, but it is a
poor substitute.
So far...
Learning Go has been an interesting experience so far. I like learning new
things, and I'm going to be using Go for some time now with the current
project. No doubt I'll have more to write about later.