I beg to differ. It forces you think about whether but not why the function itself has failed. The "why" is embedded in the type of the error (or exception) itself, but Go does not force you to examine the type of the error; indeed, sheer muscle memory compels you most of the time to just write if err != nil again and again.
> I can decide how to gracefully fall back if a failure occurs before I'm even allowed to use the successful result.
You could wrap every line of Java code in a try/catch if you wanted to (it wouldn't be idiomatic, but it's definitely possible). You're just not forced to.
For what it's worth, you also shouldn't email your ops team directly from production code. It doesn't scale. You should log the error, and your monitoring stack should handle altering the relevant team (full disclosure: I work for such a monitoring stack company). It's very rare that you actually want to recover from errors, as that's typically a pattern that leads to silent failures and difficult to diagnose issues.
The lack of forcing to handle (checked) exceptions is exactly why I dislike Java's model. Until I've checked for an error state, I want to have an Either that may or may not have the data I'm looking for (and if it doesn't, has an explanation). In a truly exceptional situation I can crash and give a 500 error, but checked exceptions are by definition supposed to be recoverable, and in a production codebase I don't want to be able to lazily avoid recovering from them.
Java never lets you ignore an non-panicking exception. Unlike Go, where you CAN ignore the error value.
Besides that, Java forces you to explicitly state what type of exceptions each function can throw.
Java exceptions are stricter than Go, not vice versa.
Now, if we were talking about .Net or JS, or Python or mostly any other language with exception I'd get this criticism, but it is patently false when it comes to Java.
An attempt to read from a Reader, for example, can fail with a java.io.IOException. The javadoc for that lists 31 direct known subclasses, including CharConversionException and JMXServerErrorException; and there is always the possibility of a custom subclass from somewhere else in your application or a 3rd party library.
You can't do anything sensible with such a broad error (like decide if retrying might be sensible), so you end up either propagating it in your type signature, wrapping it in a RuntimeException, or ignoring it.
I agree that the Java standard library (especially the older parts of it) is quite bad. The Go standard library, whatever issues it has[1], is still pretty solid.
Unfortunately, the questionable quality of the Java standard library and the Java EE libraries back in the day, have led to some of the bad patterns we see nowadays in Java, that are not necessitated by the language, e.g. gross abuse of inheritance.
I still want to point out here that error values are not superior to exceptions, since:
1. Java shows exception handling can be forced to be explicit as well.
2. Handling Go errors is NOT forced. In fact, you can always ignore the functions' return value or assign the error part of it to a `_`. This is far less explicit than an empty catch block.
[1] https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-...
This is not true, and therefore your explanation is unsatisfying.
> You can write code as if every function call is successful
How does writing code as if every function call is successful (when you know that some functions will fail) not lead to some other bit of code further up the call stack having to make a decision about an exception that it doesn't have the context for? A bit of code which probably was written by a different developer who didn't anticipate what you were going to do?
There have been frameworks like the Common Lisp Condition System to let a caller dictate a recovery/retry policy, but they never caught on. In practice "do no harm, give up, and report the error" is what almost everyone wanted, and most languages support it without punitive effort.
Copy/pasting another person's answer:
You could wrap every line of Java code in a try/catch if you wanted to (it wouldn't be idiomatic, but it's definitely possible). You're just not forced to.
You can handle some of the exceptions in the same function, without going to the extreme of wrapping every line of code in a try/catch block.
Yes you ARE forced to. Unless the exception derives from RuntimeException.
If I encode my errors as values (usually with Either), I have to decide how to gracefully fall back if a failure occurs before I'm even allowed to use the successful result. Maybe I just hide the part of the view that needed that data. Maybe I show an appropriate error message. Maybe I email operations to let them know about the problem. Whatever I do, I have to actually think about it and not just assume that the error will be caught somewhere by someone. The result is usually a dramatically improved user experience when failures inevitably occur.
Exceptions tend to pass the buck so far down the line that there's no context to make an appropriate decision. Values tend to force a decision early, when you still have enough context. (Obviously both can be used against the grain, but the question is which pattern is easier.)