9 ms·
> Nothing explodes, the code keeps running in my book, this is not a good thing but a very bad thing. If I'm a library function somewhere all the way down the
by pilif 8y ago
> Nothing explodes, the code keeps running
in my book, this is not a good thing but a very bad thing.
If I'm a library function somewhere all the way down the stack and I'm at a point where something about the state I'm working on isn't up to the specification I expect them to be, then I'd prefer the safety of blowing up.
I don't want to be responsible for my callers eventually ignoring my error code and happily churning along thinking that I have fulfilled my promise and enacted the side-effect I promised to have.
If something else, far removed from me depends on me to having had the side effect, it might blow up. Or it might write corrupt data. Suddenly a thing that went wrong at one place blows up at a totally different place which makes it incredibly hard to debug.
And if the world is burning, "hard to debug" is about the least wanted property a problem could have.
Oh no. If the world is burning for me, then I absolutely do not want to continue.
But I also do not want to abort the daemon process I'm being hosted in because that would mean affecting other threads of execution (be it threads, coroutines or whatever else) where the world might be in a totally acceptable state.
Calling `exit` in a library function is outright rude.
If I get to `throw`, I can make absolutely sure that I made it clear to my caller or my caller's caller that I panicked while there's still a chance for my parent daemon to not die but handle my panic cleanly. Or, my caller, or its caller was prepared for me failing and can chose another way out.
But by throwing I can raise a very strong signal that something is wrong and I have the guarantee that if I'm being ignored, then I will kill my parent which is totally their fault for ignoring me and it will be very obvious what happened, why it happened, and, above all, where it happened.
If I'm an engineer wearing my devops hat, I'd much rather debug an uncaught exception causing a stack trace to be logged in sentry.io (or whatever else you use. I'm a very happy sentry customer, but your might have your own tool) rather than corrupted data that might have been corrupted months ago.
- navd 8y agoI think this is where custom error types become useful. They can provide you more context about what went wrong so that you can handle that issue properly
- pilif 8y agoNot if my caller forgets to handle my error at all
- NateDad 8y agoIf you care, you can't realistically forget. There are linters that will find your missed error checks.
- hu3 8y agoIn Go, if a function returns an error you have to explicitly handle or suppress it. Otherwise code just doesn't compile. There's no margin to "forget".
- insertnickname 8y agoThat's not quite true. For instance, the error value returned from `fmt.Printf`[0] is usually ignored silently. [0] https://godoc.org/fmt#Printf https://godoc.org/fmt#Printf
- jerf 8y agoIt's true that functions that return (result, error) tuples are hard to miss the errors from, because you have to explicitly either at least accept it, or ignore it. It's both visually obvious that you've dropped the error and there are linters to catch this for you (which I highly recommend integrating into pre-commit hooks). However, if a function just returns an error, but no result value, it is indeed easy to silently drop that error without realizing it by simply invoking the function and not catching the result at all. To which, again, I'd highly recommend using linters at commit time or even code save time.
- jcelerier 8y ago> In Go, if a function returns an error you have to explicitly handle or suppress it. Otherwise code just doesn't compile. ah, yeah, great idea. in practice this just means that most Go projects are minefields of if err != nil { panic(err) } or if err != nil { return err } ie a manual & repetitive implementation of assert for the first case and manual & repetitive implementation of an exception call stack for the second. thank you very much, I'll take the automated version known as exceptions instead.
- cle 8y ago> in my book, this is not a good thing but a very bad thing. Your whole reply is a straw man. The author is claiming that nothing explodes b/c the error is correctly handled, not b/c it was silently swallowed. I've seen so many bugs caused by exceptions being silently swallowed in other threads. Exceptions are not a silver bullet here, and you end up having to pass errors as values across call stacks anyway.
- TimJYoung 8y ago"Your whole reply is a straw man. The author is claiming that nothing explodes b/c the error is correctly handled, not b/c it was silently swallowed." Yes, but I think that this is exactly what pilif is objecting to: the fact that there is no way for the callee to force the caller to deal with the error condition (I'm not sure that this is entirely correct, given that Go has the panic/recover keywords that are later discussed in the article, but then you might as well use exceptions). IMO, this is very similar to what happens with use-after-free and other types of memory reference errors: they are extremely hard to debug because the symptoms/results of the error can be totally disconnected from the original source of the error, in some cases by minutes or longer. You simply don't want to have a situation where improper state is allowed to linger and further pollute/corrupt any subsequent computations.
- zzzcpan 8y agoAnd yet forcing the caller to deal with errors still doesn't prevent improper state in any way.
- jrobn 8y agoThis is why Erlang's crash early and restart philosophy was so eye opening when I became more and more familiar with the language and design. The whole language is about managing state and managing it well in a dynamic realtime environment (versus Rust's explicit state encoding for example).
- verdagon 8y agoCan you tell me more about Erlang's approach? Quite interested in learning of new ways to deal with state and errors.
- zzzcpan 8y agoTried to explain it here: https://news.ycombinator.com/item?id=18534441 https://news.ycombinator.com/item?id=18534441
- dnautics 8y agoIt's called the "let it crash" philosophy. The idea is that things are going to go horribly wrong no matter how well your code and everyone else's code is written. If you program for resilience, that is the ability to recover from a horribly bad event, then you can get high service availability and faster development (you're not obsessed with checking for errors, you only handle the most common ones and program to capture enough errors to achieve the service level you are comfortable with or have agreed to).
- jlouis 8y agoIsolate all memory so a failure is compartamentalized Discriminate between the error-kernel of the program and the rest. The kernel is the part which is not allowed to fail. Goal: keep it small. Discriminate between errors you have seen happen, and everything else. Handle the errors you've seen. It looks a bit like Go's handling, but only for things where you know you have an error flow. There are also exceptions, but they are somewhat rarer in typical code. On an unhandled error, crash the isolated process. This produces, a state of the memory, a stack trace and eventually the last few messages sent to that process. The system has a recovery model which will restart failed processes from a last known good state. The programming language is functional, and quite much so, so there are orders of magnitude fewer errors relating to state manipulation. One typical approach is to use the crash as a way to learn about errors that can happen, then patch those errors by handling them. Because you have a strategy for reactively handling errors, it is far less dangerous to have them in th e program, as long as they are under a noise floor.