giusdp

giusdp

Refactor functions that can return different kind of errors

Hi all,

I was wondering on the best way to have a function that can return multiple errors. For example, I have something like this:

  @spec run_some_work(atom(), Params.t()) ::
          {:ok, Result.t()}
          | {:error, :bad_params}
          | {:error, :not_found}
          | {:error, :no_workers}
          | {:error, :worker_error}

The function can fail in different ways, and for each it has a different :error. in the @spec. I list them all so from the outside I can clearly see how to work with this function.

The problems that I’m having is that having functions with a big list of return types is cluttering my files, and that I have to update every spec if I add something new.

What’s your approach with functions that can give different errors? I was thinking about creating some sort of struct MyError, where the docs and specs would describe the possible errors it can contain, so in the function I would be just returning a single MyError struct.

Thanks in advance.

Marked As Solved

al2o3cr

al2o3cr

On a purely mechanical level, you can solve these problems with @type - anywhere you can write a basic type, you can instead write a custom one:

@type work_errors :: {:error, :bad_params} | {:error, :not_found} | {:error, :no_workers} | {:error, :worker_error}

@spec run_some_work(...) :: {:ok, Result.t()} | work_errors

That addresses both the “this takes up a lot of space” issue as well as “this is an updating headache” issue.

HOWEVER

A better question would be who’s handling those errors, and how. If they’re doing:

case run_some_work(...) do
  # etc
  {:error, _} ->
    yolo_try_it_again
end

then there’s not much value to maintaining detailed errors all the way up the stack. In that case, collapsing to a simpler error payload at a sensible boundary might make things clearer.

Finally, if the problem is likely to go away by just trying again (and if your data model tolerates it) you could consider “handling” errors like :no_workers by crashing.

Also Liked

dimitarvp

dimitarvp

I’m sure somebody will show up and tell me I’m wrong but I frankly don’t care about exceptions in Elixir. And the whole other throw, raise and rescue stuff. I ignore them on purpose.

I have only reached for them when it was made clear to me that a worker cannot ever crash (and thus be auto-restarted) at which point I just said “okay, fair” and bullet-proofed it.

In every single other case, so 99.9%, I’ll reach for everything else but not exceptions / throws / raises. If something is that brittle and can crash periodically – but recovers by itself little later – then I’ll just wrap it in a GenServer and communicate through messages with it (so as the caller doesn’t crash right away). But if that keeps crashing then it’s best for your app to go down because you have an unrecoverable error, in which case exceptions wouldn’t help at all anyway.


TL;DR: there’s a very good reasoning behind the “let it crash” philosophy, provided you have taken some precautions (which the OTP either gives you by default or makes extremely easy for you to set up e.g. amount of process restarts before giving up, timeouts etc).

cmo

cmo

You can use defexception to define the error, which you can pass around as a struct or raise.

You can also define the list of errors as a @type if you’re repeating them.

cmo

cmo

You’re wrong! :wink:

An exception is a struct, so you can be more specific about the cause and house more details/context than a simple atom. You don’t have to raise it.

For example, I have an external system that can return either a windows or app error code, which we turn into one of two exceptions with the code and message. I can easily distinguish between them and handle the different error cases.

Where Next?

Popular in Questions Top

pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
LegitStack
I’m hoping you guys can give me some general advice and perhaps code examples if you’re feeling up to it. I’m very interested in Elixir,...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
sabri
Can someone explain the settings of pool_size of Ecto in config file? and what is the recommend size? Thanks
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New

Other popular topics Top

srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

We're in Beta

About us Mission Statement