> I feel it may be true for programmers primarily trained and experienced in C, but is not true in general.
If you're mentally comparing against the way error handling in C, your position is much more defensible. Error handling in C requires manually releasing resources in the correct order, and it's easy for a destructor to slip by in the wrong order, to get missed entirely, or for someone to use the wrong jump label. (I'm thinking about Linux kernel style code, here.) If I'm reviewing some error handling code in C, I might have to scan up to the top of the function and count all the resources which need to be released, then look down to the bottom to see that the right label is being jumped to. That's a pain.
In languages like Golang, error handling and resource cleanup are a bit more orthogonal. You can look at a Golang call which returns an error and ask, "Does this handle the error correctly?" Usually this only requires looking at three lines of code at a time, maybe four. Resources are just released with defer, except in unusual cases.
> Once you get in the habit, it's not hard to scan through a program line-by-line asking yourself "what would happen if an exception were thrown at this point?"
I guess I never got in the habit. Maybe you have better habits than I do, or maybe I'm just not "smart enough" to be a programmer, but I find this hard, so I choose to use languages which make this easier. I find it hard in Python, hard in JavaScript, hard in C++, and hard in C#. I'm really not even sure how to e.g. handle exceptions thrown by C++ constructor failures separately from other things later in the block, e.g., I want to do this:
try {
MyType x{args, args};
} catch (SomeException &e) {
...
}
x.DoSomething();
And I'm not sure how to even write this as functional C++ without doing something weird or maybe just saying "fuck it" and wrapping x in std::unique_ptr.
> In fact, I'd wager that for anyone who's spent years working in any language with exceptions this quickly becomes second nature.
Based on what I see during code reviews, I don't agree. Raymond Chen has a good point... when I review someone's Golang code, checking for correct error handling is fast and easy, it's mostly mechanical, easy to target with lint, and I can immediately move on to looking at the code semantics. With, say, C# code, or especially Python code, there are too many cases where a method call slips inside or outside the try/catch block where it belongs.
I've also been bitten too many times by bugs in production where a function throws an exception, and we have to decode the stack trace to figure out what the error means or how the error is even possible. This is typically easier in programs written in Golang, at least in my experience.
The more syntactic sugar a language has, the more likely it is that I don't notice an error condition that is being handled incorrectly. Golang, with its simplicity, makes you do some extra typing up front but in my experience it pays for itself by going faster through code review and bug fixes.
This depends on the kind of code you're writing. If you're mostly sticking inside application memory and not doing, e.g., IO, IPC, etc, then the exception approach is better.
I'm not saying Golang is perfect by any means, but I find the way it does error handling to be a welcome reduction in my cognitive load.