Interestingly, this means that while pervasive use of "if err != nil { return err }" is technically very similar to exceptions when used everywhere, it can still have a different semantic meaning to a human reading the source.
Interestingly, this means that while pervasive use of "if err != nil { return err }" is technically very similar to exceptions when used everywhere, it can still have a different semantic meaning to a human reading the source.
In the majority of applications it's normally pretty obvious which errors you're likely to care about (the user already exists, etc.) vs the rest that you just handle at the top level. The trouble with go's error handling is it makes you care about everything, which is just a waste of time and effort because the majority of errors will be fatal anyway.
This is generally the best way to deal with errors. In most cases, only the original caller really knows which errors ought to be considered fatal, and which should result in retries or be handled in some other way.
If you handle errors deep down in the call stack, you are robbing the caller of the ability to decide how to handle them. Different callers may want different things. It is better to let errors bubble up as far as possible.
See, I get the exact opposite tendency. Exceptions mean that any statement, no matter how seemingly trivial from the caller's perspective, can fail. It means I'm constantly thinking "If an exception is thrown between X() and Y(), does it break any assumptions I've made about the program state?". You end up with hacks like putting code in finally blocks just to prevent partial execution.
I think exceptions would work fine in a language that also had built-in support for transactional memory, where you could commit or rollback operations when an exception is thrown partway through. Without any language support, though, I think that exceptions and mutable state do not play nicely with one another.
It's helpful habit for services especially because most of what you're doing is local to a request anyway and you want to "undo" or "drop" things. Exceptions in services are great because they actually preserve situational information pretty well and a catch+rethrow is pretty good for annotations at different levels. Err/panic are so poor at capturing anything of value by default. They require a ton of diligence to be useful.
The nice thing about being able to catch exceptions is that I can kinda define what operations are "atomic" on my terms.
Presumptuous example disclaimer.
For instance, if I'm processing a set of files with records, I'll probably have a substantial error domain around the processing of a record, of a whole file, and my whole program. I don't want to babysit each byte read.
Now, I admit, this does require you to write code that didn't pollute your state with partial work. Unwinding from a frame/function should leave everything in a known place.
Which is not bad at all.
In fact, it's closer to how e.g. Erlang works.
>> without `go fmt` "fixing" it
Great point.And this line should be indented relative to the 'happy path' lines!