6 ms·
People often take this paradigm too literally, especially if they have not programmed much in Elixir. It is most common to bubble up errors using error tuples,
by sb8244 3y ago
People often take this paradigm too literally, especially if they have not programmed much in Elixir. It is most common to bubble up errors using error tuples, and exceptions are available.
Fred Hebert's blog post called "it's about the guarantees" makes this more clear: https://ferd.ca/it-s-about-the-guarantees.html https://ferd.ca/it-s-about-the-guarantees.html
- throwawaymaths 3y agoIt's actually a pet peeve of mine that people use error tuples so often in elixir. Honestly most of the time the correct thing is to raise. There really isn't anything you can do anyways, and avoiding the temptation to do something about it and make spaghetti out of the decision tree is worth it.
- troupo 3y agoMany people don't write async code or use supervision trees for most tasks. A lot of code is straightforward synchronous code, so it makes sense not to throw. Once you run it in a task, then it makes less sense to do {:ok, something} on everything
- throwawaymaths 3y agoIn what situation are you not supervised? If you're in a phoenix controller, you're very much supervised (even if you didn't write that supervisor) What actually are you going to be doing with that error? Most of the time it gets raised at the end of the error chain resolution.
- troupo 3y ago> If you're in a phoenix controller, you're very much supervised (even if you didn't write that supervisor) Yes, but do you want to kill your process and restart? I've been doing quite a bit of LiveView recently, and a thrown error will kill the process and reload the page. I... don't want to do that. > What actually are you going to be doing with that error? Depends on the error :) If it's "record not found", I redirect to a 404. If it's "can't resize image because file format is incorrect" I log it, and display an error message. And so on.
- throwawaymaths 3y ago> LiveView recently, and a thrown error will kill the process and reload the page. I... don't want to do that. Ok. That's 100% real and I've run into it myself and been annoyed by it. Probably liveview should do something about that because it's breaking BEAM idioms. > redirect to 404 Raise an error with plug_status set to 404. I believe there's a protocol way of doing it too if you dont own the exception and thus can't add plug_status as a field.
- troupo 3y ago> Probably liveview should do something about that because it's breaking BEAM idioms. It really doesn't. It's the `one_to_one` restart strategy for supervision. > Raise an error with plug_status set to 404. Honestly, first time I've heard about it :) It's somewhat well hidden in the docs I think. But 404 is just one of possible responses. I might want to redirect instead. Auth errors should be also handled by redirects. And so on. Not everything is an exception for the framework supervisor to handle through custom fields.
- throwawaymaths 3y agoIt's a protocol. You can set up your custom action (including redirects) here: https://hexdocs.pm/plug/Plug.Exception.html#actions/1 https://hexdocs.pm/plug/Plug.Exception.html#actions/1 This is how phoenix does those fancy error reports in dev (but not prod) > Probably liveview should do something about that because it's breaking BEAM idioms. Actually now that I think about it, failable actions should probably be wrapped in a task. It's not hard for the liveview to subscribe to task failures and bubble it up to the user as a flash. Not doing so is breaking processes-as-failure-domains concept. The phoenix liveview documentation should probably hoist that sort of guidance to the top and put flashing lights around it.
- troupo 3y ago> You can set up your custom action (including redirects) here: But why would I do that for things that 100% predictable, can and will happen in specific flows? > failable actions should probably be wrapped in a task. It's not hard for the liveview to subscribe to task failures and bubble it up to the user as a flash. How does LiveView know an action is failable? What you propose is akin to trying to solving the halting problem :) Also, not all failures need to be propagated to the user as a flash: - an auth failure should result in a redirect, not a flash - a record not found failure could result in an in-place 404, a redirect to a 404, a redirect to a search page, in a cache lookup etc. - validation errors should be propagated to the form and displayed inline, not as a flash and so on. There are many failure modes and there are just as many recovery modes, the vast majority of which entirely depend on the business logic of your app that LiveView has no idea about
- sb8244 3y agoReturn the error to my user so they can at least see what went wrong with their request (assuming the errors are built to be user facing). The idea of hiding information from the user, just for me to get it from bug tracking and support tickets is not something I could jive with.
- throwawaymaths 3y ago> assuming the errors are built to be user facing Most errors are not? Suppose you get KeyError. Is that information useful to the user?
- sb8244 3y agoI'd say most errors I handle are user facing in some way. The specific error (going to say in response to API given the article) you gave would likely be returned through the system as API response error tuple. The parts of call stack higher up could determine if they want to show the error or swallow it. If it's a generic KeyError or MatchError, there's a 0% chance anything outside of the immediate call site could handle the error.
- sph 3y ago> Honestly most of the time the correct thing is to raise. No that's just wrong. If you want to let it crash, just pattern match on the :ok tuple. You shouldn't have to write exception handler blocks to deal with functions that raise, instead of returning a tuple, that's bad Elixir style. All functions that can fail should return a tuple. For convenience, one might also add a ! version that raises.
- throwawaymaths 3y agoNo. This is exactly wrong. Pattern matching on ok tuple is awful because you will get a generic MatchError with an inscrutable message that requires reserialization of the exception struct and not a more useful Error where you can have an informative Exception.message protocol implementation. > All functions that can fail should return a tuple. For convenience, one might also add a ! version that raises. This is absolutely good advice for library writers. But if you're using a library, most of the time, use the raising version.
- sph 3y agoYou don't get an inscrutable message. An example from Elixir docs: iex> {a, b, c} = {:hello, "world"} * (MatchError) no match of right hand side value: {:hello, "world"} The MatchError shows you the part that was not matching. When pattern matching on {:ok, value}, on match error you'd be told it doesn't match, say {:error, %DatabaseError{reason: "Server has gone away."}} I always pair my production Elixir apps with Sentry, which gives me a backtrace, so I have everything needed to track down an error. > But if you're using a library, most of the time, use the raising version. I use the raising version to explicitly "I want this to crash." So this would happen only at the edges of the system. All internal code should operate on tuples. You don't want a core, internal function to raise and have to catch it on higher-level modules, because that's terrible form. In other words, feel free to raise, as long as there is no one that has to catch the exception. If you raise, the goal is to crash, not to leverage exceptions as control flow. This isn't Java. I've been using Elixir since 2016 and I don't think I have written more than a couple exception handlers since.