$30 off During Our Annual Pro Sale. View Details »

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
    Maciej Kaszubowski
    ERROR-FREE
    ELIXIR

    View Slide

  2. mkaszubowski94
    Error handling is crucial

    View Slide

  3. mkaszubowski94
    Error handling is a big source of
    complexity

    View Slide

  4. mkaszubowski94
    def cancel_job(user_id, job_id) do
    Job
     Repo.get(job_id)
     Job.cancel_changeset(%{user_id: user_id, canceled_at: now()})
     Repo.update()
    end

    View Slide

  5. 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

    View Slide

  6. mkaszubowski94
    When designing, we focus mostly
    on the happy path.

    View Slide

  7. mkaszubowski94
    We have to design error-handling
    too.

    View Slide

  8. mkaszubowski94
    Not errors are equal

    View Slide

  9. mkaszubowski94
    Users
    Sources of errors

    View Slide

  10. mkaszubowski94
    Users
    Other services
    Sources of errors

    View Slide

  11. mkaszubowski94
    Users Infrastructure
    Other services
    Sources of errors

    View Slide

  12. mkaszubowski94
    > Developers
    Users Infrastructure
    Other services
    Sources of errors

    View Slide

  13. mkaszubowski94
    Each requires a different
    error-handling strategy

    View Slide

  14. Strategies

    View Slide

  15. mkaszubowski94
    Let it crash!

    View Slide

  16. mkaszubowski94
    Let it crash!

    View Slide

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

    View Slide

  18. 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

    View Slide

  19. mkaszubowski94
    :error tuples

    View Slide

  20. mkaszubowski94
    :error tuples

    View Slide

  21. mkaszubowski94
    • User/Client errors
    • Correct behaviour (!)
    • Predicable errors
    What?
    Users
    Other services
    :error tuples

    View Slide

  22. 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

    View Slide

  23. mkaszubowski94
    > Developers
    Users Infrastructure
    Other services
    Sources of errors

    View Slide

  24. mkaszubowski94
    > Developers
    Users Infrastructure
    Other services
    Sources of errors

    View Slide

  25. mkaszubowski94
    > Developers
    Users Infrastructure
    Other services
    Sources of errors

    View Slide

  26. mkaszubowski94
    > Developers
    Users Infrastructure
    Other services
    Sources of errors

    View Slide

  27. mkaszubowski94
    > Developers
    Users Infrastructure
    Other services
    Sources of errors
    ?

    View Slide

  28. mkaszubowski94
    > Developers
    Users Infrastructure
    Other services
    Sources of errors
    :(

    View Slide

  29. mkaszubowski94
    Controller
    Context
    Context.Xxx

    View Slide

  30. mkaszubowski94
    Controller
    Context
    Context.Xxx

    View Slide

  31. mkaszubowski94
    We're too used to the "standard" ways

    View Slide

  32. mkaszubowski94
    case some_function(args) do
    {:ok, something} 
    # 
    {:error, reason} 
    {:error, reason}
    end

    View Slide

  33. 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?

    View Slide

  34. mkaszubowski94
    Don't deal with errors.
    Prevent them.

    View Slide

  35. Eliminating errors

    View Slide

  36. mkaszubowski94
    Heuristics
    Declarative operations
    1
    2
    Reasonable defaults
    3 Don't fight the real world

    View Slide

  37. mkaszubowski94
    Heuristics
    Declarative operations
    1
    2
    Reasonable defaults
    3 Don't fight the real world

    View Slide

  38. 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

    View Slide

  39. 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

    View Slide

  40. 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

    View Slide

  41. mkaszubowski94
    Heuristics
    Declarative operations
    1
    2
    Reasonable defaults
    3 Don't fight the real world

    View Slide

  42. 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

    View Slide

  43. mkaszubowski94
    Describe the end goal,
    not the steps to reach it

    View Slide

  44. 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

    View Slide

  45. 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

    View Slide

  46. 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

    View Slide

  47. mkaszubowski94
    Heuristics
    Declarative operations
    1
    2
    Reasonable defaults
    3 Don't fight the real world

    View Slide

  48. mkaszubowski94
    def take_off_shelf(product_id, shelf_id) do
    if product_on_the_shelf?(product_id, shelf_id) do
    remove_from_shelf!(product_id, shelf_id)
    else
    {:error, :product_missing}
    end
    end

    View Slide

  49. mkaszubowski94
    def take_off_shelf(product_id, shelf_id) do
    if product_on_the_shelf?(product_id, shelf_id) do
    remove_from_shelf!(product_id, shelf_id)
    else
    {:error, :product_missing}
    end
    end

    View Slide

  50. mkaszubowski94
    def take_off_shelf(product_id, shelf_id) do
    if product_on_the_shelf?(product_id, shelf_id) do
    remove_from_shelf!(product_id, shelf_id)
    else
    notify_operators(product_id, shelf_id)
    end
    end

    View Slide

  51. mkaszubowski94
    We cannot prevent people
    from making mistakes

    View Slide

  52. mkaszubowski94
    Not all errors are bad

    View Slide

  53. mkaszubowski94
    Error handling has to be
    designed

    View Slide

  54. Why talk about this?

    View Slide

  55. mkaszubowski94
    Design is not about a single
    clever idea

    View Slide

  56. mkaszubowski94
    We cannot rely on intuition

    View Slide

  57. mkaszubowski94
    You will make wrong decisions
    Make sure you learn from that.

    View Slide

  58. mkaszubowski94
    A set of clear and simple
    heuristics and questions is really
    helpful

    View Slide

  59. mkaszubowski94
    Approach design more
    thoughtfully

    View Slide

  60. mkaszubowski94
    Use facts instead of opinions

    View Slide

  61. mkaszubowski94
    Gather as many questions and
    heuristics as possible.

    View Slide

  62. mkaszubowski94
    Gather as many questions and
    heuristics as possible.
    And share them!

    View Slide

  63. mkaszubowski94
    Share the process of making
    decisions.

    View Slide

  64. mkaszubowski94
    "A Philosophy of Software Design" - John Ousterhout
    "The art of destroying software" - Greg Young
    Credits

    View Slide

  65. mkaszubowski94
    Maciej Kaszubowski
    THANKS!
    I hope it was useful

    View Slide