4 ms·
I mostly agree. People read code like this and think: its verbose and repetitive. if err != nil { return nil, err } But, I almost never write code like t
by 015a 3y ago
I mostly agree.
People read code like this and think: its verbose and repetitive.
if err != nil { return nil, err }
But, I almost never write code like that. What I'm usually writing is some formulation of:
if err != nil { return nil, fmt.Errorf("Error fetching thing: %v" err) }
Sometimes; you wrap to add additional context. Sometimes; you wrap to get a generic error type into a package-specific error type. Sometimes; you wrap to get, idk, some kind of project-wide HTTP-oriented error type. Error wrapping is the pattern in Go; which is very different from exception-oriented languages.
I like this article's syntax for a straightforward "throw error" situation. I wouldn't support its addition, but I wouldn't oppose it either. However, I struggle to imagine a more concise Go-ish syntax I like which supports a "wrap and throw" type situation. Maybe something like:
v1 := Thing1()!
v2 := Thing2() ! fmt.Errorf("Error fetching thing: %v" err)
Phrased in english; the bang operator can follow any statement that resolves to a multi-value function return where the last value is an error type, and the statement is in a function body whose last return value is an error type. If this function returns a non-nil value as its last value; If nothing follows the bang, it bubbles up this non-nill error with no wrapping. If a statement follows the bang, that statement gains an implicit `err` value containing the error value the LHS statement resolved to; and it can return a new error-type which then gets bubbled up.
But, again; I don't love or even really like this, its just the best I can come up with. Its not that much shorter than writing it out. Its not obvious how it should behave in the presence of an outer-scoped variable named `err`. Its not obvious how the bubble up should handle the other non-error return values (zero value I guess?)
One thing I rather like about Go is; if you're catching and re-throwing wrapped errors, which is a pattern I like, its actually far more concise than exception oriented languages. The same thing in JS?
let v;
try {
v = Thing()
} catch (err) {
throw new Error(`I died: ${err}`);
}
So; you rarely do that. But in Go, its barely harder to wrap and re-throw than it is to just directly throw; so people do it more often.
- thiht 3y agoTo be fair, you wouldn't do that in JS because exceptions have a stack trace, so you don't need to add the context on every return. The downside is that since you don't "need" to catch your exceptions, you don't think about them. That's one thing I really like with Go, errors are in my face all the time, so I have to think about them. Even if I write the infamous if err != nil { return err } at least I do it consciously. And when I or my colleagues read it 2 months later, we know from reading that this can return an error, and we see clearly how it was handled, so we can think about it and visually see if it was handled correctly. Reviewing and reading code with exceptions is a nightmare, because you have no idea if errors were handled correctly. On every call you have to guess if it throws or not.