Hacker News new | ask | show | jobs
by rpercy 3423 days ago
Heh, it does feel repetitive. But after ~10 years with Java, I'm happy to avoid the cognitive load from deciding how to most politely generate/propagate an exception. Obviously, there's no perfect solution, and preference is subjective.
3 comments

In reality Go makes it worse, if you truly keep track of all possible cases. After calling a method, in Go you have to

1) "if err != nil" after every statement

2) give some serious thought to whether the previous statement could panic or not

Good luck if it's a library call that may get updated or call other libraries !

3) Think about the non-error error cases that can't be abstracted out. Go is like C, in the sense that there is an ERETRY "error" (unsurprisingly, you should simply try again, you should NOT fail)

And there are cases where there can be an error and yet error is nil. Easy example of this would be sscanf.

And we now see practical Go code published online : how these problems are dealt with, real world edition:

1) either mindlessly putting "if err != nil { return err }", which is a very bad information-erasing exception system, or just outright ignoring errors. Don't you know you can also use "_" as the error variable ? Maybe they should make that implicit like in perl. Of course perl is likely to tell you this happened ... unlike C and Go.

(really brings back the C days doesn't it ?)

2) most people either don't know or just deny this. Thankfully panics at least do list where they occur. They also kill your program and print stacktraces. Pages and pages and pages of stacktraces.

3) very few people even know about these problems ... so they're ignored, and the standard Go tools themselves don't behave according to unix specifications.

Panic is pretty much never used (so you don't really have to think if the previous statement can panic. If it does it means something is SO wrong that your application can't continue anyways because it's broken)

You should really use a library like github.com/pkg/errors so you get to wrap the error you return with additional information. Errors are just a worse Either monad after allm they're much more pleasant to use than exceptions.

> Errors are just a worse Either monad after allm they're much more pleasant to use than exceptions.

How are errors in Go at all monadic? They just have a convention where you return a tuple and manually check if something isn't nil. Either type will inhabit one variant or another, not both with one having a value of nil.

The 'monadic' part of Either (or Result in something like Rust, where try! is sort of like >>= if you squint) is the ability to chain Either types together and have the boilerplate abstracted, that feature is completely absent from Go. Errors in Go are neither the Either type nor monad, IMO.

As I said, in practice they're a worse either monad. With which I meant -> they were inspired by them, but lack the monad part and having the value with the error in one object interchangeably. It's the same treating errors as values though.
It's really just a worse Either type, if anything, there's nothing monad about it. Although it's sort of hard to be close to an Either without being an algebraic data type. But I see what you're trying to say.
> Panic is pretty much never used (so you don't really have to think if the previous statement can panic. If it does it means something is SO wrong that your application can't continue anyways because it's broken)

That's it. I think OP just assumed panics in Go are what exceptions are in other languages.

Because the stdlib is using them more and more? As someone mentioned, even closing an already closed channel can panic. Calling the random number generator with a negative limit panics.

And there’s nothing like checked Panics so you’d know if one will happen or not.

As I said, if your code is plainly wrong then I see no problem in it going haywire. The channel rules are widely known -> you know, it takes more than a few days to learn a language, there are a few rules to learn too.

Calling a number generator with a negative limit is also a programmer fault, so no reason to provide an error here.

Yes, there is no problem as long as you agree with X about what is a "real" error and what isn't.

X includes

the language authors (who are using this more and more in the stdlib as pointed out elsewhere)

the authors of any library you use ... but

transitively, so this includes the authors of any library you use indirectly as well

Hmmmm ... what was the problem with (unchecked, or python's) exceptions again ?

I see nothing wrong with that - those _are_ fatal conditions that indicate that something is dangerously wrong in the program.

And those panics do give you a helpful stack trace, complete with source code line numbers, so it's easy to find the culprit (as opposed to "bubbling up" exceptions).

These uses of panic look a lot like the canonical use of unchecked exceptions in Java: the programmer has made an error, which they could have avoided. It's a bug. There's no point returning an error and letting the program try to recover, just blow up with as much information as you can, so the bug can be fixed.

The canonical use of checked exceptions in Java is for unpredictable events - almost always related to interaction with the outside world, like IO, networking, parsing, etc. These are things the programmer can't prevent, and must defend against, so the type system allows, and in fact forces, the programmer to explicitly address them.

This is all explained beautifully, and at length, in this monograph:

http://joeduffyblog.com/2016/02/07/the-error-model/

It's the same cognitive load, really. Instead of deciding how to best propagate exceptions, you're now deciding how to best propagate the error value.
Not really. In a language like Java, some of the questions I'd have to ask myself are:

  - Should I try to catch the exception, or just let it 
    bubble up and edit my interface to include it?
  - Should I create a new exception type or reuse an existing one?
  - Should I throw a checked or unchecked exception?
  - Am I exposing implementation details via my interface? 
    (eg I don't want to throw an SQLException from GenericDataSourceWidget.connect())
In golang, I know there's really just the one pattern: check if err != nil, prepend a descriptive message, and return it.
>check if err != nil, prepend a descriptive message, and return it.

That'd be the RuntimeException equivalent, sure. But what do you do for the equivalent of checked exceptions? Errors are frequently recoverable, "err != nil" alone does nothing to help you there, and string manipulation is a horrific alternative to types.

You can have typed errors too. This is fairly common in Go. As long as it implements the Error interface.
At which point you're back to the original complaint of:

  - Should I try to catch the exception, or just let it 
    bubble up and edit my interface to include it?
  - Should I create a new exception type or reuse an existing one?
  - Am I exposing implementation details via my interface? 
    (eg I don't want to throw an SQLException from GenericDataSourceWidget.connect())
(except for "- Should I throw a checked or unchecked exception?" since that's fairly Java-specific)

To me, those questions seem unavoidable, and sweeping them under the rug is a false simplicity. You're exposing things - what do you expose? How should the caller deal with it? Is it the same as [other thing]? I'd much rather have the type system involved, since error handling is pretty critical to correctness/stability. If go's giving up the safety, what does it get in return?

I feel like the type system is involved? If there's a problem it's that Go doesn't make throwing typed errors a common convention (and we lack the tooling to make handling them a habit I.e. inspect this call and find me all the error types which can come back.
> I'm happy to avoid the cognitive load from deciding how to most politely generate/propagate an exception

This is like saying "I'm happy to avoid the cognitive load of having to specify how my code should behave in case of an error".

Sure, your code is simpler as a result. It's also more buggy.