Live data from Hacker News

Exploring Error Handling Patterns in Go

8thlight.com

51–60 of 80 posts

Re: Exploring Error Handling Patterns in Go

#51
post #45

Earlier quoted context omitted.

Not really, in all the years I've been writing Go, only one library used panics for error handling. Usually if something panics you don't want to handle it. (Other than at the http handler level, where you can just throw an InternalServerError and log the panic) First and foremost, you can usually assume libraries won't panic, though it would be nice to have a tool (grep) to check for explicit panics.

If something "errors", you usually don't want to handle it either. Other that at the http handler level.

Actually, you often do. That's the point really. You should decide if it's an operation you might want to retry, you might also want to just flat out error and do nothing more, maybe you want to provide degraded functionality, like provide some default answer.

I think errors as values cause you to always think about this, which makes you handle errors in a more sensible way, instead of just bubbling up. Sure, 90% of situations you will bubble up, but in my opinion it's still worth it.

Re: Exploring Error Handling Patterns in Go

#52
post #28
post #11

I wish more developers could do "investigation" like this for a new languages they learn. For me, the main difference between Go's way of handling language and the rest of mainstream languages is that it makes error handling unmagical . It literally says – errors are just like any other return values. Let's say, if you have function `sqrt` and return a value, and then call this function – you probably is interested i…

> I've heard from many devs that Go was the reason that made them appreciate proper error handling. I agree that in some way, this is actually a good reason to support Go's tedious way of handling errors. Yet, it's akin to avoiding functions (because stackframes are magical), or "for" loops (because their condition block is magical), or threads (because: magic). There is only so much a non-toy programming language sh…

My first (real world) exposure to exceptions was Python. I was a bit above novice. I would write some code, run it, and it would die of some horrible exception. I would catch that exception, try again, and die again. Lather, rinse, repeat. Eventually I would wrap everything in try-except blocks. Talk about verbose; tab indents everywhere. You can often end up handling exceptions somewhere not immediately close to the call that failed.

I liked when I started with Go (mostly due to concurrency primatives, but errors were nice too). In my day job, many of the errors have a need for custom handling and I get that for "free" in Go and I am never surprised by a program crash because I failed to read the docs on a function and what exceptions it may throw (or undocumented exceptions it may throw due to one of its dependencies). I can see right in the signature that I have an error to potentially handle.

Re: Exploring Error Handling Patterns in Go

#53
post #49

Earlier quoted context omitted.

Except it's just a returned value and not a goto lookalike.

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 your function can be found with "grep return".

Exceptions, by default, break the natural control flow unless you wrap everything with try/catch. Even then, a lot of constructs won't be very natural and you really will have to get out of your way to identify the source of the exception, which very frequently is more important than it's type.

Re: Exploring Error Handling Patterns in Go

#54
post #44
post #7

Earlier quoted context omitted.

This is not correct. Exceptions do different things than report an error. They unwind the stack. That's why they are called exceptions and not errors. One important benefit of Go's error handling pattern is readability. With exceptions, it's not easy to see who handles it and where. There is indeed less code, and that's nice for the writer, but from the reader perspective, error handling becomes obscure. And from the…

The parent is correct. Returns also unwind the stack. > One important benefit of Go's error handling pattern is readability I beg to differ, Go's approach is similar to checked exceptions, Java's original sin. And just like checked exceptions, forcing the invoker of a function to handle the error directly is the wrong approach in the vast majority of cases. It just produces code noise and catch/wrap/throw style code,…

Seriously, exceptions are very different from returning an error. Confusing error return with checked exception tells it all. A checked exception is just an exception type specification.

When you read code with a call to a function returning an error, you see how the error is handled. With exception, unless there is a try/catch close surrounding the call, you don't know where and how an exception is handled.

To me, throwing exceptions is like littering the streets. That feels fine for the one who does it, because he assume someone else will take care of the mess. But the problem is taking care of it, who, how when ? With big projects, this strategy is unmanageable.

Correctly handled exceptions don't make middle-ware easier to write or more readable. On the contrary.

You know that programs are not only http handlers, right ?

Re: Exploring Error Handling Patterns in Go

#55
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…

Abstractions like map and filter that have decades of use and countless pages of research behind them are not the wrong abstraction. You are more likely to get the wrong abstraction by forcing programmers to create their own abstractions instead of letting them use well-known ones that have been refined over many years.

Re: Exploring Error Handling Patterns in Go

#57
post #47
post #35

Earlier quoted context omitted.

Easy, just assume it throws. That's the case anyway. Thanks to panics, even in Go. Edit: Also, there is no parallel control flow. Languages with exceptions have union-type return values, and every statement is implicitly followed by the equivalent of: if err!=nil return nil, err. The fact that in Go you have to type that makes Go cumbersome, not smart.

I'm not sure I understand the relationship between union-types and exceptions. You mean something like ? Could you expand on that? > Thanks to panics, even in Go. Fortunately, for some reason Go developers don't use panic like exceptions and recover them at library boundaries.

Yes, exactly like that. In real world programs, every function might fail, even the simplest ones (stack overflow, out of memory, interrupts, etc). No information is gained by declaring a specific function might fail. Also it's almost certainly a lie to declare: this function will never fail. So if every function might fail, why not just produce a union type for every function return type. Also, lets automatically check for the error case after each nested invocation, and cleanly unwind the stack (returning ErrorData again) on failure. This makes 95% error handling code go away. The ErrorData type uses a special return keyword ("throw"), and in rare cases, errors need to actually be handled, so the union's ErrorData type is exposed to the user code with additional primitives (catch). On all the code in between, the ErrorData type is just hidden behind the scenes.

Re: Exploring Error Handling Patterns in Go

#58
post #11

I wish more developers could do "investigation" like this for a new languages they learn. For me, the main difference between Go's way of handling language and the rest of mainstream languages is that it makes error handling unmagical . It literally says – errors are just like any other return values. Let's say, if you have function `sqrt` and return a value, and then call this function – you probably is interested i…

The flipside of easy-to-learn is there's no payoff for getting better with the language. Your code will always be exactly as tedious as novices' code because they'd rather conserve compiler cycles than spend them to amplify programmers' work.

"The flipside of easy-to-learn is there's no payoff for getting better with the language. Your code will always be exactly as tedious as novices' code because they'd rather conserve compiler cycles than spend them to amplify programmers' work."

In my years of experience with the language, this is, bluntly, untrue. Go, used properly, is slightly more verbose than most comparable code in Python or Perl. If someone is writing code that is shot through with boilerplate in Go, then I would say that they may be using "Oh, Go just needs lots of boilerplate" as an excuse.

The problem isn't that Go lacks abstraction mechanisms; the problem is that you need to learn how to use the ones that are there and not sit there pining for the ones that are not. I find this to be almost exactly like learning Haskell; you need to learn to use what is there, not sit there pining for what you don't have. Also like Haskell, there are some particular points that it all comes together at once and hurts you, but, then again, there's some places in Go where I've had big wins using the language features too. It does cut both ways. (I've done some fun things with interfaces, and the pervasive io.Reader/Writer support, while not necessarily a feature of the language, can make certain things amazingly easy to do while still retaining incredible flexibility.)

As one example I went through personally, while by the time I learned Go I had a lot of non-OO experience, so I wasn't as stuck on inheritance as someone who only did OO-languages for the last 10 years would be, I still had to adjust to using a generally-OO language (by my standard of the term) that did not support inheritance. It has now been literally plural years since I missed inheritance in Go. (In fact, quite the opposite; I miss easy composition in my other OO languages! Yes, Virginia, it is possible to miss features Go has when using other languages, despite what it may seem like if you only read the criticisms.) But my first couple of months were a bit rougher before I internalized how the composition works and affects the design of your code.

Complaining that Go code is all boilerplate is like someone who tried Haskell but complains that it's just an especially inconvenient imperative language and you end up doing everything in IO anyhow. Nope... you have not yet gotten past your "Writing X in Y" phase. That's fine; there's a ton of languages and platforms and libraries in the world. If you didn't get a short-term payoff from using it, go ahead and move on. But you haven't attained enough mastery to go around slagging on the language/platform/library yet.

(And, again, let me say that, yes, it is somewhat more verbose that Python or something. If you've shrunk your Go down to that level, you probably went too far and are doing something ill-advised. But I find that in practice, for most tasks, it is not that much more verbose. There are exceptions, like heavy duty GUI code or (IMHO) scientific code; the solution is not to use Go for those.)

Re: Exploring Error Handling Patterns in Go

#59
post #28

Earlier quoted context omitted.

> I've heard from many devs that Go was the reason that made them appreciate proper error handling. I agree that in some way, this is actually a good reason to support Go's tedious way of handling errors. Yet, it's akin to avoiding functions (because stackframes are magical), or "for" loops (because their condition block is magical), or threads (because: magic). There is only so much a non-toy programming language sh…

My first (real world) exposure to exceptions was Python. I was a bit above novice. I would write some code, run it, and it would die of some horrible exception. I would catch that exception, try again, and die again. Lather, rinse, repeat. Eventually I would wrap everything in try-except blocks. Talk about verbose; tab indents everywhere. You can often end up handling exceptions somewhere not immediately close to the…

Maybe you would like the concept of "checked exceptions" from Java? Exceptions are part of a method signature and not handling them is a compiler error, but they're still exceptions, not return values.

Re: Exploring Error Handling Patterns in Go

#60

Earlier quoted context omitted.

Why? I am genuinely curious. Terrible compared to what?

I actually really like Go error handling (I write Go daily), but truth be said, they're a poor mans Either Monad.

The thing that bugs me is that if you ignore the error (by simply not checking for it), it's still there, possibly insidiously corrupting runtime state. Imagine trying to debug a file format corruption that happened because some obscure part of the code tried to add to the format and instead errored (silently) and added garbage and then the code just kept chugging along until the state REALLY messed things up.

The thing many programmers don't seem to realize is that a program is a model of a design in the programmer's mind. If the model goes off the rails of the expected design/behavior in any way, that should be considered very bad ASAP... or as many languages treat it, "exceptional".

Post reply on HN