GoLang First Impressions


I recently had the chance to write a small application (around 700 lines) in Go. I spent the free parts of my weekend writing tests for it. Both processes have been pretty interesting. It’s probably a good idea to write them down and think about them.

The Setup

Urban Airship, where I work as a web engineer, had a hack weeek a few weeks ago. Usually I pick some front-endy problem for free coding time. Over the last hack week, I built out functionality (which we never deployed) to allow end users to upload and manage content in our CDN. This would be useful since our unwary customers regularly DDOS themselves with static asset requests resulting from push traffic.

This time I decided to work with one of our Core Data Engineers on a twitter-firehose-like event stream. Clients would connect over HTTP, encoding any state information as URL parameters, and, so long as the HTTP connection stayed open, our service would spew new-line delimited JSON blobs down the pipe. There would be two pieces, a gatherer which collects events from the wild, and an HTTP server that faces some paying subset of the public. The two pieces would pass messages via an Apache Kafka 0.8 instance. The gatherer and the HTTP server know nothing about each other. They happen to both write protobufs they both know how to read to what happens to be the same a Kafka instance. I wrote the HTTP layer in Go.

The Experience

I stepped into this project having done no more than a few hours of Go. I’d gotten a web server responding to HTTP requests by reading and writing to disk. I think it’s a testament to the simplicity of the language and the rich example set (not to mention shopify’s sarama), that I was able to successfully complete this project. I even had enough time to complete a little web app which connects to the service and displays the streaming results.

Writing it was a pleasurable if somewhat bewildering experience. Go is the first statically, strictly typed language that I have written, as well as the first langauge I’ve written that implements interfaces. It’s still weird to me how interchange-able types are. For example, the http.HandleFunc takes a ResponseWriter as it’s first argument, but if I make a type assertion and do some legardemaine with interfaces, I can use a method that was never implemented on the ResponseWriter at all. Somebody on the google group asked had the same confusion.

Testing And Error Handling

Over the weekend I started testing, and here is where the real fun begins. Testing actually requires a more intimiate knowledge of how the underlying system works, which is hard for me since although I can combine the pieces using reason, and can build theories about what will work and test them, my knowledge of the language’s fundamentals is week.

This is especially fun when the bits you don’t quite understand yet are throwing the errors.

I ran into my first channel deadlock errors. So far, every experience I’ve had interpreting tracebacks from concurrent processes has been uniformly terrible. The relevent information is either absent (*cough* Javascript *cough*) or burried in a haystack.

Error handling in general in go is repetetive. I’m getting really tired of writing:

if(err == nil) {
  // handle the error, but probably just exit early:
  return
}

Apparently panicing is a no-no, (I’m still not quite sure why yet), and you can’t really wrap this in a function since it needs to accomplish flow control where ever it lives (especially if I’m in a for loop and the error shouldn’t end execution. So far, the solution that feels best to me is actually a goto statement, much belittled as that may be. It seems kind of perfect for this case:

for event := range(my_cool_channel) {
  err := parseBytes(event, eventStruct)

  if(err != nil) {
    goto REPORT
  }

  err := validate(eventStruct)

  if(err != nil) {
    goto REPORT
  }

  resp, err := getAdditionalData(event)

  if(err != nil) {
    goto REPORT
  }

  event, err = reincorporate(event, resp)

  if(err != nil) {
    goto REPORT
  }

  event_channel <- event

  continue

  REPORT:
    Logger.Print(err)
    error_channel <- err
}

It has the advantage of not losing scope, not introducing an additional function, describing its own flow control, and being pretty clear, to be quite honest. It has the disadvantage of near universal ridicule on the part of the programming community at large.

The alternatives are essentially to wrap it in a function or just repeat myself. Both of which are probably fine, but a function seems a little silly for a piece of logic so tightly coupled to this loop. Repeating myself is, well, repeating myself. I’d frankly rather not.

Composition

The final frustration I have with go is how difficult it is to compose operations.

There’s no way to splat a multi-return function into a struct, and there’s no way to pass multiple return values directly from one function to the next without first assigning them to variables.

Conclusion

All that said, I found writing golang to be a very pleasant experience. Having a compiler is awesome. Types help you avoid a whole class of languages, and the tooling around the language is very good. Each tool is fairly small, and I think the people developing the project considered use cases very similar to mine. The vim integration went very smoothly.