> In order to write resilient software, programs must handle not only the "happy path" when things succeed, but the path where things might fail
And exceptions let you handle error conditions without making the actual business logic harder to read, with as little or much specificity as required.
> Thus it is important for developers to 1) be aware of which operations may fail fail, and b) think about what the program should do in that case
Checked exceptions/effect types exist, being explicit or implicit in function signatures is not a fundamental property of exceptions.
And what is clearer in terms of error handling — if err being every third line, with questionable handling logic, e.g. just printing or swallowing stuff (or gestures at the article), and definite human error from repetition —— or a well-defined block with proper scoping, without which the error case does the only reasonable thing — automatically bubbles up, making it possible to handle higher up. There is often no immediate action that can be done in certain exceptional situations, e.g. your ordinary function that writes a file can’t do anything about a full disc. The best it can do is to yell, so that the action that called it somewhere can do some evasive action, e.g. re-trying/notifying the user/etc.
> Exceptions make it easier for the programmer to forget that something might fail, and to avoid thinking about what to do if it does fail.
Disagree. If anything, something not being in a try-catch block says that it will be handled higher up (or checked exceptions making it part of the signature), and when it’s surrounded by it, I know what is the happy path, and unhappy path immediately, without it being crossed over (usually badly), as it would happen with if errs.
> Go's error handling idiom makes it clear that an operation might fail
What about the case when it both returns a value and an error?
> and prompts programmers to think about what to do in that case
Blindly if erring and printing out a random string is not error handling. That’s just noise, and a terrible trap for yourself, having to grep for useless error codes later on.
> If anything, something not being in a try-catch block says that it will be handled higher up
I don't think you get what I'm saying. Some functions will always succeed. Some functions fail in obvious ways. Some functions fail in non-obvious ways. How do you know, as you're scanning a long block of code, which operations may fail, and which will always succeed?
For instance, suppose you have code like the following:
// Decode they key JsonKeyGuids as type []Guid
guids := JsonGetKey[[]Guid](&ru[i].Json, JsonKeyGuids)
Without looking at the function signature: If the key in the structure doesn't exist, what happens -- does it throw an exception, or return an empty value? Is it possible for JsonGetKey to fail to parse?
And while checked exceptions might help, it's not perfect: Suppose your code block calls functions a(), b(), and c(); all of them return ErrParseFail, but while it's pretty obvious that a() or c() might fail that way, it's not at all obvious that b() would.
Secondly, even for operations that are obvious may fail: maybe you, as a senior programmer who has programmed with exceptions for years, are paranoid enough that you're always thinking in the back of your mind "what happens if this fails?" But I very much doubt a junior programmer is going to have that habit. Part of the intent of Go was to have a language for Google which would allow junior programmers could come to a previously unfamiliar bit of code and be reasonably effective very quickly.
> And what is clearer in terms of error handling — if err being every third line, with questionable handling logic, e.g. just printing or swallowing stuff (or gestures at the article), and definite human error from repetition
You'd almost never just print the result of error messages unless it's at the top level, or it's the equivalent of a script. In most cases, you bubble it up, often wrapping it with a message of what you were trying to do; e.g.:
return nil, fmt.Errorf("trying to excluded guid list for %v: %w", ru[i].Id, err)
That way at the top level (or wherever you do log the message), you have a stack not just of the function names and line numbers, but what the program was actually trying to do, potentially with specific values involved.
Not having the equivalent of C's "must_check" is certainly a missing guard-rail in golang
> And what is clearer in terms of error handling...
It comes down to a judgement call. I think Golang's way is better. Yes, it makes the code look cluttered with exit paths, but that's because the code is cluttered with exit paths.
I can see that with experience, an exception-based developer would learn to see the implicit exit paths in most cases. So let me assert to you, that with experience, a check-the-return-values based developer also learns to filter out the explicit error paths to see the "happy path" algorithm clearly. But on the whole, I think the latter is likely to lead to fewer bugs, particularly for less experienced developers, but even for more experienced developers.
At any rate, now you've heard arguments for Go's error handling idiom; and if you don't agree, at least you can understand where the Golang crowd are coming from.
And exceptions let you handle error conditions without making the actual business logic harder to read, with as little or much specificity as required.
> Thus it is important for developers to 1) be aware of which operations may fail fail, and b) think about what the program should do in that case
Checked exceptions/effect types exist, being explicit or implicit in function signatures is not a fundamental property of exceptions.
And what is clearer in terms of error handling — if err being every third line, with questionable handling logic, e.g. just printing or swallowing stuff (or gestures at the article), and definite human error from repetition —— or a well-defined block with proper scoping, without which the error case does the only reasonable thing — automatically bubbles up, making it possible to handle higher up. There is often no immediate action that can be done in certain exceptional situations, e.g. your ordinary function that writes a file can’t do anything about a full disc. The best it can do is to yell, so that the action that called it somewhere can do some evasive action, e.g. re-trying/notifying the user/etc.
> Exceptions make it easier for the programmer to forget that something might fail, and to avoid thinking about what to do if it does fail.
Disagree. If anything, something not being in a try-catch block says that it will be handled higher up (or checked exceptions making it part of the signature), and when it’s surrounded by it, I know what is the happy path, and unhappy path immediately, without it being crossed over (usually badly), as it would happen with if errs.
> Go's error handling idiom makes it clear that an operation might fail
What about the case when it both returns a value and an error?
> and prompts programmers to think about what to do in that case
Blindly if erring and printing out a random string is not error handling. That’s just noise, and a terrible trap for yourself, having to grep for useless error codes later on.