You look at what I'm doing as a more tedious and error-prone version of exception bubbling, but that misses the forest for the trees. The whole point of doing it this way is to lift errors out of the shadows of the exception control flow path, and put them front-and-center in the actual logic of the application. Programming (in many domains) is error handling, the error handling is at least and arguably more important than the business logic.
I don't want exceptions. I do want this (or something like it).
> Even worse, with this style of programming, someone up the stack who would actually want to handle these errors has no mechanism to do, since you're returning the same type from both error cases.
As the author of this module, I get to decide what my callers are able to see. What I've written is (IMO) the most straightforward and best general-purpose example, where callers can still test for the wrapped errors if they need to. If it were important for callers to distinguish between Read and Certificate errors, I would use sentinel errors e.g. var ErrCert (if the details weren't important) or custom error types e.g. type CertificateError struct (if they were).
Adding this stuff isn't bloat. Again, it's just as important as the business code itself.
> Go's error handling strategy is its weakest aspect, and it is annoying to hear advocates pretend that Go is doing it right
In industry, considering languages an organization can conceivably hire for, and considering the general level of industry programmers -- programs written in Go are consistently in the top tier for reliability, i.e. fewest bugs in logic, and fewest crashes. Certainly more reliable than languages with similar productivity like Python, Ruby, Node, etc.
There are plenty of flaws in Go's approach to error handling -- I would love to have Option or Result types, for example -- but I think, judging by outcomes, it's pretty clear that Go is definitely doing something right.