Live data from Hacker News

Exploring Error Handling Patterns in Go

8thlight.com

71–80 of 80 posts

Re: Exploring Error Handling Patterns in Go

#71
post #34

What is not covered here, and what I'm still searching for a good pattern for, is being able to return different errors depending on the type of failure. Suppose you have a function that fetches a model from your database. It can return an error if the given user doesn't have permission to fetch this model, or it can return an error if your db connection barfs for some reason. The calling function needs to be able to…

There's another one I often use: Create a custom error type, for example DB Error: type DBError struct { Temporary bool NetworkBased bool Cause error } Now you can provide functions like IsTemporary(err). Otherwise, you can use 2# with a twist, instead of matching on a type, you can do: switch { case isErrFetchForbidden(err): case isErrFetchNotFound(err): } or even: IsBadRequest(err) IsInternal(err) IsTimeout(err)

So you then define your function to return the type DBError instead of a generic err type. That makes sense to me but for some reason some of the stuff I've suggests that just returning err is more go-like.

Re: Exploring Error Handling Patterns in Go

#72
post #53

Earlier quoted context omitted.

Exceptions—which go straight up the call stack, like returns—are nothing like gotos.

The similarity in both needing to have stack trace does not make them very alike yet. They serve similar purpose and for that purpose the stack trace serves value to the developer. Returns go straight up the stack only if you choose them to. Caller doesn't have to propagate errors and return immediately, it can hold onto them and/or process them in the natural place they occur, they are just values. The returns from…

Why would finding the source of the exception be difficult? The stack trace in exception-based languages goes back to the actual line, as opposed to a stack trace from an error-as-value based language, where it only goes to where you trigger generating the stack trace.

Re: Exploring Error Handling Patterns in Go

#73
post #71

Earlier quoted context omitted.

There's another one I often use: Create a custom error type, for example DB Error: type DBError struct { Temporary bool NetworkBased bool Cause error } Now you can provide functions like IsTemporary(err). Otherwise, you can use 2# with a twist, instead of matching on a type, you can do: switch { case isErrFetchForbidden(err): case isErrFetchNotFound(err): } or even: IsBadRequest(err) IsInternal(err) IsTimeout(err)

So you then define your function to return the type DBError instead of a generic err type. That makes sense to me but for some reason some of the stuff I've suggests that just returning err is more go-like.

No, you don't, DBError should implement the error interface.

IsTemporary also takes an error and does something like:

  if err, ok := errors.Cause(err).(*DBError) {
    return err.temporary
  } else {
    return false
  }

Re: Exploring Error Handling Patterns in Go

#74
post #32
post #10

Earlier quoted context omitted.

At the cost of making the entire logic's readability less which to me is more important than sometimes getting confused where errors bubble up to. The philosophy is different when, for example the author of Ruby wanted to make coding fun for programmers and does a good job at it and Go is sticking to 'this must be right' approach and breaks some people's heart. Personally I'd appreciate being more 'fun'.

Depends on your definition of fun, I guess. I personally don't like Ruby because of that fun-factor. In most cases, it makes programming easier for the novice, but more complicated for the experienced. This is because to make it easier for the novice, there are all kinds of constructs that try to make the code imitate normal English. But coding software is a completely different thing than writing text, thus the Engl…

That's only the postfix version of unless though, and you could use if the same way. And inversely, you can use unless is its own block, like if.

Re: Exploring Error Handling Patterns in Go

#75
post #42
post #37

Earlier quoted context omitted.

I don't think it's because of the complexity. I'm quite sure it would not have been difficult to do. One of the reason is that you don't always want to check the error. The most common one is fmt.Println. I would not like to always write _, _ = fmt.Println("Hello, playground")

You certainly do not always want to write _,_ = fmt.Println("Hello, playground"), and I think this points out the real lack. What do you want your program to do if fmt.Println starts failing? I think, unless you are explicitly checking for errors, that in all other cases you want it to crash. Rather than silently continue. Which is a bug, and a potentially disastrous one, that is endemic in Go code (and, to be fair,…

If fmt.Println fails you've likely got bigger problems and the game is over.

And if it means a lot in this case, wrap the function and use it instead.

Re: Exploring Error Handling Patterns in Go

#76
post #24

Earlier quoted context omitted.

Saving the work of understanding abstractions usually means you'll pay the cost of sieving through explicit duplication.

A little duplication is better than the wrong abstraction, and I've seen far more subtly wrong or obfuscating abstractions than duplication in code I have to manage. Go is definitely not perfect, and sometimes it's plain wrong about this (I don't particularly like the go error handling and hope it improves), but there is a reason for discouraging certain types of abstraction and encouraging verbosity and boring code…

> A little duplication is better than the wrong abstraction

Besides the notion of "The wrong abstraction", which is sometimes used as a proxy for "Abstractions I don't want to learn", we're discussing about language level abstractions here. The article you quote criticizes user level abstractions.

Language-level abstractions have a decent enough track record that we can assess them. Go even uses some of them: GC is, after all, an abstraction.

There would also be a lot to say about what the "little" in "A little duplication" means.

Re: Exploring Error Handling Patterns in Go

#77
post #71

Earlier quoted context omitted.

So you then define your function to return the type DBError instead of a generic err type. That makes sense to me but for some reason some of the stuff I've suggests that just returning err is more go-like.

No, you don't, DBError should implement the error interface. IsTemporary also takes an error and does something like: if err, ok := errors.Cause(err).(*DBError) { return err.temporary } else { return false }

oooh!

Re: Exploring Error Handling Patterns in Go

#78
post #53

Earlier quoted context omitted.

The similarity in both needing to have stack trace does not make them very alike yet. They serve similar purpose and for that purpose the stack trace serves value to the developer. Returns go straight up the stack only if you choose them to. Caller doesn't have to propagate errors and return immediately, it can hold onto them and/or process them in the natural place they occur, they are just values. The returns from…

Why would finding the source of the exception be difficult? The stack trace in exception-based languages goes back to the actual line, as opposed to a stack trace from an error-as-value based language, where it only goes to where you trigger generating the stack trace.

I meant finding it in the context of a coder who writes a function. It's hard to identify which expression and statements can cause exceptions and effectively short-circuit your function. In contrast to explicit errors-returned-as-values.

(Panics of course can cause similar thing in Go but that's the reason why they should rarely be recovered and not used as a value propagation mechanism.)

Re: Exploring Error Handling Patterns in Go

#79
post #19

Earlier quoted context omitted.

On the bright side, your code will always be as easy to understand as a novice's code too, so it conserves other programmer's mental cycles as well.

> your code will always be as easy to understand as a novice's code too Yeah, that seems about perfectly correct. Have you looked at novice's code? Is it easy to understand?

Yes, we have adopted Go for a lot of things at work and I have reviewed code of many of our devs who are more or less Go novices. It has indeed been pretty easy to understand - much easier than other languages (notably Java in this regard, but Python falls afoul of it a bit too) where people tend to write things in quite different styles and/or with excessive abstraction that made it much harder to understand what was going on.

Re: Exploring Error Handling Patterns in Go

#80
post #78

Earlier quoted context omitted.

Why would finding the source of the exception be difficult? The stack trace in exception-based languages goes back to the actual line, as opposed to a stack trace from an error-as-value based language, where it only goes to where you trigger generating the stack trace.

I meant finding it in the context of a coder who writes a function. It's hard to identify which expression and statements can cause exceptions and effectively short-circuit your function. In contrast to explicit errors-returned-as-values. (Panics of course can cause similar thing in Go but that's the reason why they should rarely be recovered and not used as a value propagation mechanism.)

Is that any worse than the ambiguity in e.g. code like this?

    func thing(arg) {
      otherThing(arg) // does this return an error you're ignoring?
    }
It even has a similar problem, where void returns -> err returns on code changes are not visibly discoverable (similar to a newly-throwing func).

Granted, you could turn this into a compiler error. But it's not currently.

---

Anyway. Given that so many not-prevented-by-the-typesystem operations panic, I don't think it's reasonable to assume that any func call will not panic, especially not in the future since they may change. So you already have to program with `defer` to maintain your invariants, which is exactly the same as with exceptions, except you now have to deal with both possibilities for nearly every func.

Post reply on HN