Live data from Hacker News

Exploring Error Handling Patterns in Go

8thlight.com

21–30 of 80 posts

Re: Exploring Error Handling Patterns in Go

#21

A cursory look at the article, shows that the most important observation about error handling in Go is missing. Errors should be "decorated" (wrapped, contextualized...) in 99% of the cases. In the end you get errors that describe step by step what your program tried to do and why it failed, for example: * could not load profile: could not open file: permission denied. * could not download profile image: could not op…

In my experience, this just becomes arbitrarily close to "re-implement your stack trace by hand with space-delimited words instead of camelCaseFunctionNamesOrWhatever".

I'll overwhelmingly prefer an always-correct stacktrace over a hand-recreated one that sometimes collapses multiple branches into a single ambiguous on. At least then the devs can help me when it fails. And stack traces and concatenated strings are in no way appropriate error responses for humans unless you're expecting them to be able to navigate the source code, so neither does anything for the "provide a helpful error message for non-programmers" problem.

---

this is why stuff like https://github.com/pkg/errors exists. wrap at the deepest level / where the error originates, and it's relatively rare that you need to add context at higher levels. If you want user-friendly errors, you need something dramatically more sophisticated.

Re: Exploring Error Handling Patterns in Go

#22
post #5

Earlier quoted context omitted.

And it’s—bluntly—a terrible design.

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

I used to forget to put "set -e" in bash scripts. Then one went off and deleted a whole bunch of important stuff despite a prerequisite command erroring. Now I include it, but remembering one line of code in the header is easy compared to Go's approach of remembering to check every error.

Re: Exploring Error Handling Patterns in Go

#23
post #5

Earlier quoted context omitted.

And it’s—bluntly—a terrible design.

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

Exceptions.

Anders give an interview in 2003 [1] where he talks about how C# looked to learn from Java's checked exceptions. His conclusion was basically that, in their evaluation, 9/10 exceptions cannot be handled beyond some generic top-level handler.

If this observation is correct, and it certainly aligns perfectly with my own, then bubbling makes a lot more sense.

Note that, with error return values, you can emulate bubbling (which is what most Go programmers end up doing). And with exceptions, you can emulate return values. The question is what's the most common default? And, again, according to Anders, as well as any project I've ever worked on, bubble-by-default is overwhelmingly the most useful thing to support cleanly.

The only way Go's approach makes sense is if you consider it's original goal (system programming) and MAYBE (i don't know, I'm not a system programmer) for such systems you can/need to handle each error. Except that's not really how Go is being used now, so...

[1] https://www.artima.com/intv/handcuffs.html

Re: Exploring Error Handling Patterns in Go

#24

Earlier quoted context omitted.

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.

That's a feature, not a bug. It means I don't have to deal with anyone's 'clever' code. It's not about saving the compiler work at all, it's about saving the hundreds of humans who have to read your code after you the work of understanding the abstractions you created.

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

Re: Exploring Error Handling Patterns in Go

#25
post #21

A cursory look at the article, shows that the most important observation about error handling in Go is missing. Errors should be "decorated" (wrapped, contextualized...) in 99% of the cases. In the end you get errors that describe step by step what your program tried to do and why it failed, for example: * could not load profile: could not open file: permission denied. * could not download profile image: could not op…

In my experience, this just becomes arbitrarily close to "re-implement your stack trace by hand with space-delimited words instead of camelCaseFunctionNamesOrWhatever". I'll overwhelmingly prefer an always-correct stacktrace over a hand-recreated one that sometimes collapses multiple branches into a single ambiguous on. At least then the devs can help me when it fails. And stack traces and concatenated strings are in…

Just using WithStack() from "github.com/pkg/errors" on any error that originates from outside my repository has been my go-to rule for any Go project. It has never disappointed.

Re: Exploring Error Handling Patterns in Go

#26
post #23

Earlier quoted context omitted.

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

Exceptions. Anders give an interview in 2003 [1] where he talks about how C# looked to learn from Java's checked exceptions. His conclusion was basically that, in their evaluation, 9/10 exceptions cannot be handled beyond some generic top-level handler. If this observation is correct, and it certainly aligns perfectly with my own, then bubbling makes a lot more sense. Note that, with error return values, you can emul…

I've always been annoyed by the parallel control flow introduced by exceptions in any language. They are used so often in many languages where it doesn't feel necessary.

The fact that I don't even have to think if the function call I'm looking at can throw and if I should catch it or not outweighs everything.

Re: Exploring Error Handling Patterns in Go

#27
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.

That's precisely why Go is loved.

Re: Exploring Error Handling Patterns in Go

#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 should compromise in order to accomodate beginners. People may draw different lines here, but Exceptions (in garbage collected languages) are so completely unmagical, that the line should definitely not be drawn here. In fact, they do exactly what return nil, err does, thousands of times, over and over. In Go you can enjoy writing that code yourself. Also, Go's language designers accepted defeat when they had to add panics. I bet that beginners now just make the mistake of ignoring them instead.

Re: Exploring Error Handling Patterns in Go

#29
post #24

Earlier quoted context omitted.

That's a feature, not a bug. It means I don't have to deal with anyone's 'clever' code. It's not about saving the compiler work at all, it's about saving the hundreds of humans who have to read your code after you the work of understanding the abstractions you created.

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

This. Either you check in a DSL, or you try to compile the DSL to boilerplate in your head and check that in, then everyone has to try to read the boilerplate and try to infer what the DSL would have said.

Re: Exploring Error Handling Patterns in Go

#30
post #24

Earlier quoted context omitted.

That's a feature, not a bug. It means I don't have to deal with anyone's 'clever' code. It's not about saving the compiler work at all, it's about saving the hundreds of humans who have to read your code after you the work of understanding the abstractions you created.

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 instead, and it's not to save the compiler time.

https://www.sandimetz.com/blog/2016/1/20/the-wrong-abstracti...

Post reply on HN