Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> I constantly see code with the same verbose error checking that simply shoves the error further up the stack.

I found this tragicomically illuminating:

https://anvaka.github.io/common-words/#?lang=go

The four most common words in Go programs are literally: "if", "err", "return", "nil". If that doesn't tell you the designers missed the boat on something, I don't know what does.

What I find frustrating is the attitude that went along with it. I think it's reasonable to say, "Yeah, error-handling is important but we think Java-style exceptions have too many problems. We don't want to do that, but we're not sure what a better alternative is yet. It's a hard problem."

But instead, it's: "Our proposal instead ties the handling to a function - a dying function - and thereby, deliberately, makes it harder to use. ... If you're already worrying about discriminating different kinds of panics, you've lost sight of the ball."

It's never the language being wrong, you're just using it wrong.

Good error-handling is hard. Users want to be forced to handle errors that they want to ensure are handled, but not annoyed by errors they don't care about. Telling which from which is highly contextual, and the language rarely has that knowledge.



I don't know. I find the distinction of "panic if it's a bug, return err if it's a normal condition" to work pretty well.

It's much easier for me to write robust server software in Go than in either Java or Python -- because I don't constantly have to be thinking, "oh, is this thing going to suddenly unexpectedly throw?"

Basically, exceptions for exceptional conditions is good. But Java and Python use exceptions for normal conditions, which is cumbersome and annoying.

So for me, Go's approach to error handling is an improvement over just exceptions. The fact that it's verbose is fine if maybe not ideal.

(My vague understanding (just from reading, not using) is that's rusts is similar but more ergonomic. Ditto for Zig.)


The problem in Go isn't that the semantics are wrong, it's that the error handling is so prevalent that it starts obscures your logic. I'm writing an app right now where almost every piece of high-level code is:

  if err := doThis(); err != nil {
    return err
  }
  if err := doThat(); err != nil {
    return err
  }
  if err := doThisOtherThing(); err != nil {
    return err
  }
  // ...
The patterns in Go code are typically either (1) "call this and bail if error" or (2) "call this and bail if error, wrapping the error in a new error", with a sprinkling of (3) "if the error is io.EOF or whatever, do something else".

The #1 case is so dominant that Go really cries out for a built-in error-propagation mechanism.

The other problem with Go's error handling is the use of multi-value return types. I suspect Go's designers thought this was elegant, but it confuses things, because all error-returning functions return either a value or an error. Never both. It's not two return values in actuality, it's a sum type (aka product type or union). I'd take this over the current scheme:

  func GetFile() (io.Reader | error) { ... }
And then maybe:

  switch t := GetFile().(type) {
    case io.Reader: ...
    case error: return err
  }
which can have the following semantic sugar:

  r := try GetFile()
Rust has a macro that does something like the above, but the type system supports sum types and matching already.

Now you can do:

  r := try GetFile()
  count := try r.Read(buf)
instead of:

  r, err := GetFile()
  if err != nil {
    return err
  }
  count, err := r.Read(buf)
  if err != nil {
    return err
  }


agreed; there's real (potential) niceness in these ideas.


>The four most common words in Go programs are literally: "if", "err", "return", "nil". If that doesn't tell you the designers missed the boat on something, I don't know what does.

Are you trying to make a case for exceptions? If so, this isn't it. The "boat" the designers missed are Result types, or more generally "generics". I'm willing to bet most go function signatures are of f(...) (T, error). The if a,err := g(); err != nil boilerplate could be solved with Rust's `?` operator. For example:

    var exp int
    if a, err := compute(); err == nil {
        exp = a
    } else {
        return nil, err
    }
could be rewritten as

    exp := compute()?
Rust has `Result<T,E>` which makes this "simple". A `?` style operator in go would probably drastically reduce the mount of `err != nil` code in the wild.

At the same time, the code is still being shoved up the stack.


I'm not making a case for any specific language feature. I like exceptions, result types, and Icon's notion of failure. Just something that's better than what Go has today.


I too, have found error handling in Go to be quite good even if it is verbose. I like knowing where exactly an error has come from and that the language makes me think about how to handle them before continuing.

> ...but not annoyed by errors they don't care about. Telling which from which is highly contextual, and the language rarely has that knowledge.

Isn't this where the programmer's knowledge + a little thinking come into it? With experience you come to understand what kind of errors certain functions will return and then you spend a little time thinking about the context this error may appear. With this you determine whether the error has to be handled or can be safely ignored via a '_'.


With this you determine whether the error has to be handled or can be safely ignored via a '_'.

In my experience most of the time I want to neither explicitly handle the error nor ignore it. I want it to propagate up to a top-level handler that logs it and either fails the current operation or aborts the entire program.


The Icon programming language has a nice error handling style that reminds me of Go's local error handling but with less boilerplate. Functions can return success/failure along with the actual return value, like an implicit Maybe/Option type.

Converting the article's example code from Go:

  f, err := os.Open("file.txt")
  if err != nil {
    // handle error here
  }
to pseudo-Icon:

  if f := os.Open("file.txt") then
    read(f)
  else
    handle error here
https://en.wikipedia.org/wiki/Icon_(programming_language)


iirc it works like that in swift:

if let file = os.Open(file) { do sth } else { handle error }




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: