Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Error-free Elixir

Error-free Elixir

Error handling can greatly increase the complexity of the system. In Elixir, the default way of reducing the error-handling code is to use the "Let it crash!" approach. But there's another way.

In this talk, you'll learn how to eliminate errors by redefining the operation semantics, all supported by real-life examples. By doing so, you'll be able to not only reduce the need for error handling, but also simplify the entire codebase, make it smaller, more testable and easier to understand.

Maciej Kaszubowski

July 18, 2019
Tweet

More Decks by Maciej Kaszubowski

Other Decks in Programming

Transcript

  1. mkaszubowski94 def cancel_job(user_id, job_id) do with {:ok, job}  fetch_job(job_id),

    :ok  can_cancel?(job, user_id) do job  Job.cancel_changeset(%{user_id: user_id, canceled_at: now()})  Repo.update() else nil  {:error, :not_found} {:error, :already_canceled}  {:error, :already_canceled} {:error, :already_completed}  {:error, :already_completed} {:error, :no_permissions}  {:error, :no_permissions} {:error, %Changeset{} = changeset}  {:error, changeset} end end
  2. mkaszubowski94 Infrastructure Other services Let it crash! • Incorrect behaviour

    • Bugs hard to reproduce • Bugs hard to recover from What?
  3. mkaszubowski94 Infrastructure Other services Let it crash! • Incorrect behaviour

    • Bugs hard to reproduce • Bugs hard to recover from What? Why? • Protect invariants • Restarting can help
  4. mkaszubowski94 • User/Client errors • Correct behaviour (!) • Predicable

    errors What? Why? • Client can interpret the error • Client can recover from errors Users Other services :error tuples
  5. mkaszubowski94 Things to consider Can the layer above interpret the

    error? 1 2 How is the error possible? 3 Can the layer above fix the error?
  6. mkaszubowski94 defmodule Accounts do @spec get_settings(id())  {:ok, t()} |

    {:error, :not_found} def get_settings(user_id) do case Repo.get_by(Settings, user_id: user_id) do %Settings{} = settings  {:ok, settings} nil  {:error, :not_found} end end end
  7. mkaszubowski94 defmodule Accounts do @spec get_settings(id())  {:ok, t()} |

    {:error, :not_found} def get_settings(user_id) do case Repo.get_by(Settings, user_id: user_id) do %Settings{} = settings  {:ok, settings} nil  {:error, :not_found} end end end
  8. mkaszubowski94 defmodule Accounts do @spec get_settings(id())  {:ok, t()} def

    get_settings(user_id) do case Repo.get_by(Settings, user_id: user_id) do %Settings{} = settings  {:ok, settings} nil  {:ok, Settings.default()} end end end
  9. mkaszubowski94 def cancel_job(user_id, job_id) do with {:ok, job}  fetch_job(job_id),

    :ok  can_cancel?(job, user_id) do job  Job.cancel_changeset(%{user_id: user_id, canceled_at: now()})  Repo.update() else nil  {:error, :not_found} {:error, :already_canceled}  {:error, :already_canceled} {:error, :already_completed}  {:error, :already_completed} {:error, :no_permissions}  {:error, :no_permissions} {:error, %Changeset{} = changeset}  {:error, changeset} end end
  10. mkaszubowski94 def cancel_job(user_id, job_id) do with {:ok, job}  fetch_job(job_id),

    :ok  can_cancel?(job, user_id) do job  Job.cancel_changeset(%{user_id: user_id, canceled_at: now()})  Repo.update() else nil  {:error, :not_found} {:error, :already_canceled}  {:error, :already_canceled} {:error, :already_completed}  {:error, :already_completed} {:error, :no_permissions}  {:error, :no_permissions} {:error, %Changeset{} = changeset}  {:error, changeset} end end
  11. mkaszubowski94 def cancel_job(user_id, job_id) do with {:ok, job}  fetch_job(job_id),

    :ok  can_cancel?(job, user_id) do job  Job.cancel_changeset(%{user_id: user_id, canceled_at: now()})  Repo.update() else nil  {:error, :not_found} {:error, :already_canceled}  {:error, :already_canceled} {:error, :already_completed}  {:error, :already_completed} {:error, :no_permissions}  {:error, :no_permissions} {:error, %Changeset{} = changeset}  {:error, changeset} end end
  12. mkaszubowski94 def cancel_job(user_id, job_id) do with {:ok, job}  fetch_job(job_id),

    :ok  can_cancel?(job, user_id) do job  Job.cancel_changeset(%{user_id: user_id, canceled_at: now()})  Repo.update() else nil  {:error, :not_found} {:error, :already_canceled}  {:ok, job} {:error, :already_completed}  {:error, :already_completed} {:error, :no_permissions}  {:error, :no_permissions} {:error, %Changeset{} = changeset}  {:error, changeset} end end
  13. mkaszubowski94 "A Philosophy of Software Design" - John Ousterhout "The

    art of destroying software" - Greg Young Credits