I did Go for years, but stopped doing any serious work in it about a year ago. In general, I found it a chore to maintain Go-based systems. There's a lot to like about Go. But it doesn't seem pragmatic for the types of applications I see people using it for. For example, the entire error thing is absurd. Anders Hejlsberg got this right many years ago: 9 out of 10 errors are "handled" by a central error handler (log +…
>I found it a chore to maintain Go-based systems My opinion is the opposite. Almost every go code base I've seen is extremely consistent compared to other languages. Going from one code base to another is almost always seamless because learning Go involves learning the Go tools which forces you to follow Go coding standards. I'd choose to work on a Go code base over Java or C++ any day of the week. I don't have much…
This is not true for almost all software outside of embedded domains. In fact responding to an error near where the error occurred is almost always a mistake. The rare exceptions to this are when "error" is unavoidable due to concurrency (e.g. I/O), and even then many errors shouldn't be handled (misconfiguration, incomplete system initialization, etc.) or should be wrapped and transformed for human consumption (e.g. user tried to access a resource that couldn't be found - we still bubble up).
> you need to catch all the errors and think critically about what should be done when that error is encountered.
If you support this position, you'd support checked exceptions like Java - something that's widely considered a mistake.
There's lots of reasons why it's a mistake. A more important reason has to do with dynamic construction of control flow. If you use any monad design patterns in the construction of your application (easy to do because the monad design pattern is so powerful), you quickly create boundaries that errors need to cross unmolested because they're generic and cannot possibly encode policy. The same criticisms follow through into dependency injection and other forms of modular decomposition that use dynamic recomposition. Java style has you doing utterly pointless things, like wrapping all the implementation errors in a generic module-level error - obscuring the very thing that might theoretically let user code react to the error state!
More philosophically, abstractions fail in implementation-dependent ways. When you encode policy about failure outside the abstraction boundary, you're encoding implementation-dependent behaviour; but you can't put the policy inside the abstraction boundary either, because then the abstraction isn't reusable - different applications have different policies. Error handling is inherently opposed to abstraction. That's why microscopic error-handling close to the cause of the error only flies in small closed systems, like kernels, databases, embedded.