top | item 44172898

(no title)

jamamp | 9 months ago

I like Go's explicit error handling. In my mind, a function can always succeed (no error), or either succeed or fail. A function that always succeeds is straightforward. If a function fails, then you need to handle its failure, because the outer layer of code can not proceed with failures.

This is where languages diverge. Many languages use exceptions to throw the error until someone explicitly catches it and you have a stack trace of sorts. This might tell you where the error was thrown but doesn't provide a lot of helpful insight all of the time. In Go, I like how I can have some options that I always must choose from when writing code:

1. Ignore the error and proceed onward (`foo, _ := doSomething()`)

2. Handle the error by ending early, but provide no meaningful information (`return nil, err`)

3. Handle the error by returning early with helpful context (return a general wrapped error)

4. Handle the error by interpreting the error we received and branching differently on it. Perhaps our database couldn't find a row to alter, so our service layer must return a not found error which gets reflected in our API as a 404. Perhaps our idempotent deletion function encountered a not found error, and interprets that as a success.

In Go 2, or another language, I think the only changes I'd like to see are a `Result<Value, Failure>` type as opposed to nillable tuples (a la Rust/Swift), along with better-typed and enumerated error types as opposed to always using `error` directly to help with error type discoverability and enumeration.

This would fit well for Go 2 (or a new language) because adding Result types on top of Go 1's entrenched idiomatic tuple returns adds multiple ways to do the same thing, which creates confusion and division on Go 1 code.

discuss

order

barrkel|9 months ago

My experience with errors is that error handling policy should be delegated to the caller. Low level parts of the stack shouldn't be handling errors; they generally don't know what to do.

A policy of handling errors usually ends up turning into a policy of wrapping errors and returning them up the stack instead. A lot of busywork.

XorNot|9 months ago

At this point I make all my functions return error even if they don't need it. You're usually one change away from discovering they actually do.

billmcneale|9 months ago

> If a function fails, then you need to handle its failure

And this is exactly where Go fails, because it allows you to completely ignore the error, which will lead to a crash.

I'm a bit baffled that you correctly identified that this is a requirement to produce robust software and yet, you like Go's error handling approach...

haiku2077|9 months ago

On every project I ship I require golangci-lint to pass to allow merge, which forces you to explicitly handle or ignore errors. It forbids implicitly ignoring errors.

Note that ignoring errors doesn't necessarily lead ti a crash; there are plenty of functions where an error won't ever happen in practice, either because preconditions are checked by the program before the function call or because the function's implementation has changed and the error return is vestigal.

pphysch|9 months ago

> which will lead to a crash

No it won't. It could lead to a crash or some other nasty bug, but this is absolutely not a fact you can design around, because it's not always true.

ignoramous|9 months ago

At this rate, I suspect Go2 is an ideas lab for what's never shipping.

LinXitoW|9 months ago

I have to ask, in comparison to what do you like it? Because every functional language, many modern languages like Rust, and even Java with checked exceptions offers this.

Hell, you can mostly replicate Gos "error handling" in any language with generics and probably end up with nicer code.

If your answer is "JavaScript" or "Python", well, that's the common pattern.

jamamp|9 months ago

In primarily throwable languages, it's more idiomatic to not include much error handling throughout the stack but rather only at the ends with a throw and a try/catch. Catching errors in the middle is less idiomatic.

Whereas in Go, the error is visible everywhere. As a developer I see its path more easily since it's always there, and so I have a better mind to handle it right there.

Additionally, it's less easy to group errors together. A try/catch with multiple throwable functions catches an error...which function threw it though? If you want to actually handle an error, I'd prefer handling it from a particular function and not guessing which it came from.

Java with type-checked exceptions is nice. I wish Swift did that a bit better.