|
My personal experience at a startup with a large amount of Go code running its frontend and backend servers. YMMV. First off, I also find the interface-nil thing to be a frustrating edge-case. I also know that this design was the result of some difficult tradeoffs, as such things often are. We can argue about whether they made the right tradeoff, but I don't think it's fair to refer to it as a "mindblowingly wrong idiocy". Second, on error handling. Having now written a large amount of server code in Go, I find that I strongly support their approach, even when I find it a bit verbose. Here's how I find it actually plays out in practice: // You write something like this:
result, err := getResults()
if err != nil {
return err
}
// Then you reuse err for the next call
result, err := getResults()
if err != nil {
return err
}
stats, err := computeStats(result)
if err != nil {
return err
}
Eventually, you realize that `return err` just isn't enough most of the time, because it's impossible to make sense out of your logs. So we added a simple "error chaining" function that allows you to pass context when you return the error, which makes the logs much clearer than just a single error message, or raw stack trace. And of course it is often the case that you want to do more logging in your error blocks, even as you ignore the error and move on (e.g., "spurious error reading memcache entry; falling back to the slow thing").The practical reality of writing good server code is that exceptions which get caught way up the stack are inscrutable in your logs (at least that was our experience), so it makes sense to know up-front exactly where failures can happen, and to be forced to think about them the first time you write your code. Yes, it can be a bit verbose, but we've found the tradeoff to be net positive. |
But I think it's a good indicator of how parts of Go's design are flawed from the outset. Design is hard to change later, so it's important to get it right from the start.
Again, I totally buy explicit error propagation. I just think Go's solution ends up cluttering your code. It's so focused on errors, yet handling isn't a first-class construct, and the mechanisms it gives you for dealing with errors aren't very good. You have the cast check (a, ok := ...) and you can do a type switch (switch a := err.(type) { ... }), but it's rather weak for something that permeates every corner of the language. At least us have pattern matching.
I find the stack frame problem disappointing. It's amazing that there are now several libraries to create artificial stack frames (eg., https://github.com/facebookgo/stack) just so you get this.