What are your recommendations on method signatures and return values?

Ha! I used to do the same thing for the same reason. But then yeah, I noticed that it just makes pattern matching more cumbersome for no real reason.

And that’s the beauty of pattern matching, right? Matching

case foo do
  {:ok, nil}
end

Is arguably just as easy (or harder?) than…

case foo do
  :ok
end
1 Like

Personally, a convention that works best for me is

:ok | {:ok, value} | {:error, exception}

where the exception is well-defined as

defmodule MyApp.Error do
  @type t :: %__MODULE__{code: atom(), details: binary() | Ecto.Changeset.t()}
  defexception [:code, :details]

  def make(code, details), do: %__MODULE__{code: code, details: details}
end

I’m using it universally in the whole application. I even wrapped Ecto.Repo return values to follow that convention. The reason why it works so well for me is that I freakin’ love with statement and that structure lets me easily decide if I can handle an error or not. If not, I let the error bubble up, so eventually, it reaches a generic error handler presenting an error to the end-user. code is used in pattern matching, details is a sensible message I can eventually present.

For example

with {:ok, user} <- Users.get_user(id),
     {:ok, order} <- Orders.get_order(order_id),
     :ok <- Orders.confirm_order(user, order):
       ...
else:
      {:error, %{code: :not_found}} -> handle_not_found_error(),
      # unexpected errors usually simply bubbles up.
end

The generic handler only returns known errors to the users, replacing all the unknown ones with a generic one

  @possible_create_errors [
    :token_invalid,
    :token_expired,
    :params_invalid
  ]

  def create(conn, params) do
    hide_unknown_errors @possible_create_errors do
      with {:ok, data} <- validate_params(real_session_params(params)),
           {:ok, session} <- Sessions.create_real_game_session(params, data.endpoint_id) do
        render(conn, "detail.json", %{record: session})
      end
    end
  end

hide_unknown_errors is a simple macro logging an error if it was unexpected and replacing it with a generic one. It’s a Phoenix app, so I’m defining action_fallback rendering error to the user.

All in all, it works well for me, so I thought I could share :upside_down_face:

1 Like