It's time for a retrospective post: After seven months of off-and-on porting, I've finished my first "complete" web project in Go. I'm happy with how a few things worked out, but this post focuses on the other stuff. I've already written up my thoughts on templates, and I'm pretty happy with how that approach worked.

Middleware and Request Context

I used negroni. I liked it, but I think I may have overused it, too. The next project will start with built-in middleware: panic recovery, request/response logging and serving static files. Before I add any of my own, I'll write it as a http.Handler or http.HandlerFunc first.

I tried using gorilla's context library for storing per-request info. It was fine, but I'm going to try out Google's net/context next.

Exceptions

I missed having stack traces. Some of that pain could have been solved if I caught more bugs with small unit tests, but something always slips through the cracks. Sometimes you just need a line number. I'm going to try out go-errors/errors from Bugsnag — they wrote an introduction to go-errors here. I'm interested to see how much of a hassle it will be to change my uses of err == Expected to errors.Is(err, Expected) .

Silly Scope Mistakes

You may have seen this already: the := operator interacts with variable shadowing in a way that surprised me.

func find ( toFind valueType , items [] valueType ) ( bool , error ) { found := false for _ , item := range items { found , err := compareDangerously ( toFind , item ) if err != nil { return false , err } if found { break } } return found , nil }

This function can never return true , because the name found refers to two different bool s. := prefers to declare variables; any names on the left-hand side that were declared at a different scope will be shadowed, not assigned to. When I use Go on a multi-person team, I'll add a lint step to the tests that treats implicitly shadowed variables as an error.

Auto Reloading

I want my server to reload whenever I make a code change. After a little searching, I found some code from rsc that works, with a little effort. Here's how I changed my server to use it:

listen := fmt . Sprintf ( "0.0.0.0:%d" , * addr ) if flag . Arg ( 0 ) == "LISTEN_STDIN" { log . Printf ( "Running dev server over stdin" ) l , err := net . FileListener ( os . Stdin ) if err != nil { log . Fatal ( err ) } log . Fatal ( http . Serve ( l , nil )) } else { log . Printf ( "Listening on %s

" , listen ) log . Fatal ( http . ListenAndServe ( listen , nil )) }

It looks like devweb may have been built with Windows in mind -- it runs go build -o prox.exe [1st argument] , and then runs prox.exe LISTEN_STDIN . The above code handles LISTEN_STDIN ; the last step is to make sure your PATH is set up right. I'm running it like this: PATH=$PATH:. devweb . .

What Next?

Unlike the first project, this one is going to be closed source, because it's a reimplementation of an internal tool we use at Demiurge Studios to manage our localization database. It's a decent fit for Go — in particular, I'm looking forward to improving our asynchronous exports, which should be relatively easy to manage with a single-process server and goroutines.