I'd be very interested to see non-BEAM approaches to enabling this - i kind of end up in the same pattern thanks to "expected? Return an Error . Unexpected? Throw." However, the supervising part is then difficult. How do people approach this in Python? NodeJS? Rust? .NET?
Its common for me in PHP land to call die("Error! $e"); when something really bad has happened in a request and I really don't want anything to happen
How “let it fail” leads to simpler code
21–30 of 165 posts
Re: How “let it fail” leads to simpler code
#22I've seen a lot of new developers shocked by this approach, which surprises me a little. They seem to think that it's up to the application to handle all errors, even those of the programmer(s). This, of course, is unreasonable since it would essentially require knowing all the bugs in advance. :-)
Well, there's software that can cause some degree of harm. For example through servos controlling something physical. While you still probably can't catch all of the issues, you damn better try as hard as you can within reason. I'd also wish for similar rigor from people developing whatever filesystens my data is on. :-) Fail fast is generally a good idea, if you can do it safely.
1. Determine the last sane state of the system, and work forward from there. (Read the servo position and try to go from there)
2. Have a the "recovery" routine to reset the system. (Take all positions to "zero")
3. Just stop. (Yes, I know this can be bad). And ask a human for help.
Re: How “let it fail” leads to simpler code
#23I don't agree with this approach. Say you have a network service that relies on other network services. It is not difficult to write those such that they know to back off / retry when something disappears. It's extremely useful in a lot of situations: if you do work on a laptop that gets regularly unplugged, having running test services that know to reconnect makes your life easier. In production, having things autom…
Definitely write code to handle network issues. Don't write code to handle random bitflips, ways to handle garbage coming back from the service you're connecting to, or try to handle OOM errors. Just let those fail.
Do not catch everything you can. That's the whole point of "let it fail". An app crashing is totally fine and expected behavior, in a lot of cases (of course if it's not fine, e.g. someone dies, don't do that but if you're working on that kind of software and taking advice from me, you're super duper screwed).
Re: How “let it fail” leads to simpler code
#24For all the hate that Java tends to get, the language natively supports this distinction between: * Expected errors - Checked Exceptions * Unexpected errors - Unchecked Exceptions Idiomatic Java also makes heavy use of asserts, e.g. using the Guava Preconditions library.
The problem is that while Java the language itself does support that distinction, a lot of built-in stuff really messes it up. For example, exceptions from closing a file are unexpected, but are an IOException which is checked anyway. Also, even the support that is in the language isn't first-class; e.g., lack of exception polymorphism.
Perhaps some sort of language level solution could have been found (eg: have explicit interfaces to mark exceptions as expected or unexpected and then exceptions are assigned that using generics or something), but that ship has sailed long ago.
Re: How “let it fail” leads to simpler code
#25I don't agree with this approach. Say you have a network service that relies on other network services. It is not difficult to write those such that they know to back off / retry when something disappears. It's extremely useful in a lot of situations: if you do work on a laptop that gets regularly unplugged, having running test services that know to reconnect makes your life easier. In production, having things autom…
In python this is
@retry.retry(exceptions=[RpcException], tries=5, backoff=2, jitter=1)
def my_external_rpc_call(...):
....
And RpcException will only be raised beyond this if the backend is unreachable for ~30 seconds.Similarly, rpc services can abstract over this entirely, grpc (and presumably others) allow you to configure the retry policy per rpc service or method, and have it reflected everywhere that is used, without writing wrappers[0].
Which really all is to say, once you have solid libraries that handle retries of operations that are known to be error prone (file IO, network IO, things that could lock/block, etc.) you pretty quickly get into "any error implies we're totally boned".
[0]: https://github.com/grpc/grpc-go/blob/f601dfac73c9/examples/f...
Re: How “let it fail” leads to simpler code
#26I think that the "let it fail" approach is often inevitable, even when we try to use Result . Often, we see an "unknown" variant in the error enum, as a catch-all for a library's unexpected errors. Then, anyone who calls them must also have an "unknown" enum. And anyone who calls them, and so on. In the end, this "unknown" variant is similar to a panic, in that there's very few reasonable reactions to it: Log it, can…
Sometimes you want to return 200 even if most of the backends fail. Sometimes one part may want to retry based on any error.
Even aside from this, disallowing exceptions leads to a very predictable control flow, and makes program state able to be expressed in the type system, which is useful for many reasons on it's own.
While yes, it's often just like an exception or panic, I'll take that over exceptions in my code any day
Re: How “let it fail” leads to simpler code
#27I've seen a lot of new developers shocked by this approach, which surprises me a little. They seem to think that it's up to the application to handle all errors, even those of the programmer(s). This, of course, is unreasonable since it would essentially require knowing all the bugs in advance. :-)
Well, there's software that can cause some degree of harm. For example through servos controlling something physical. While you still probably can't catch all of the issues, you damn better try as hard as you can within reason. I'd also wish for similar rigor from people developing whatever filesystens my data is on. :-) Fail fast is generally a good idea, if you can do it safely.
Software fails, you can make failures rarer, but you can't make they go away. You have to deal with it, it's not an option.
Re: How “let it fail” leads to simpler code
#28try { ... } catch (e) { console.log(e) }
then you should probably just "let it fail" since you can't actually handle the error
Re: How “let it fail” leads to simpler code
#29the best TLDR I've seen of this philosophy is - if you find yourself writing all over your codebase try { ... } catch (e) { console.log(e) } then you should probably just "let it fail" since you can't actually handle the error
Re: How “let it fail” leads to simpler code
#30Earlier quoted context omitted.
The problem is that while Java the language itself does support that distinction, a lot of built-in stuff really messes it up. For example, exceptions from closing a file are unexpected, but are an IOException which is checked anyway. Also, even the support that is in the language isn't first-class; e.g., lack of exception polymorphism.
I think that's a symptom of the fact that the distinction is really artificial at the language level anyway. Whether something is expected or not is a function of the requirements. Even OutOfMemory can be expected and handled in certain types of applications (esp. since it gets thrown for things like file handles rather than true memory). And then there are all kinds of cases where routine exceptions like file not fo…