From 6b32731aa1e60e20f8012f88290a5963a1807389 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Mon, 11 Sep 2023 17:12:17 +0200 Subject: [PATCH] Apply suggestions from code review --- .../anti-patterns/design-anti-patterns.md | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/lib/elixir/pages/anti-patterns/design-anti-patterns.md b/lib/elixir/pages/anti-patterns/design-anti-patterns.md index d04b4f4141d..6e9aeb8a024 100644 --- a/lib/elixir/pages/anti-patterns/design-anti-patterns.md +++ b/lib/elixir/pages/anti-patterns/design-anti-patterns.md @@ -146,20 +146,19 @@ TODO #### Problem -This anti-pattern refers to code that forces developers to handle exceptions for control-flow. Exception handling itself does not represent an anti-pattern, but this should not be the only alternative available to developers to handle an error in client code. When developers have no freedom to decide if an error is exceptional or not, this is considered an anti-pattern. +This anti-pattern refers to code that forces developers to handle exceptions for control flow. Exception handling itself does not represent an anti-pattern, but this should not be the only alternative available to developers to handle an error in client code. When developers have no freedom to decide if an error is exceptional or not, this is considered an anti-pattern. #### Example -An example of this anti-pattern, as shown below, is when a library (for example, `MyModule`) forces its clients to use `try .. rescue` statements to capture and evaluate errors. This library does not allow developers to decide if an error is exceptional or not in their applications. +An example of this anti-pattern, as shown below, is when a library forces its users to use `try/1` statements to rescue raised exceptions and handle different cases. Such a library library does not allow developers to decide if an error is exceptional or not in their applications. ```elixir defmodule MyModule do def janky_function(value) do if is_integer(value) do - #... - "Result..." + "It's an integer" else - raise RuntimeError, message: "invalid argument. Is not integer!" + raise "expected integer, got: #{inspect(value)}" end end end @@ -167,62 +166,60 @@ end ```elixir defmodule Client do - # Client forced to use exceptions for control-flow. - def foo(arg) do + # Client forced to use exceptions for control flow. + def print_janky_function(arg) do try do - value = MyModule.janky_function(arg) - "All good! #{value}." + result = MyModule.janky_function(arg) + "All good! #{result}." rescue - e in RuntimeError -> - reason = e.message - "Uh oh! #{reason}." + exception in RuntimeError -> + "Uh oh! #{exception.message}." end end end ``` ```elixir -iex> Client.foo(1) -"All good! Result...." -iex> Client.foo("lucas") -"Uh oh! invalid argument. Is not integer!." +iex> Client.print_janky_function(1) +"All good! It's an integer." +iex> Client.foo("Lucas") +"Uh oh! expected integer, got: \"Lucas\"" ``` #### Refactoring -Library authors should guarantee that clients are not required to use exceptions for control-flow in their applications. As shown below, this can be done by refactoring the library `MyModule`, providing two versions of the function that forces clients to use exceptions for control-flow: +Library authors should guarantee that users are not required to use exceptions for control flow in their applications. As shown below, this can be done by refactoring `MyModule`, providing two versions of the function that forces clients to use exceptions for control flow: -1) A version with the raised exceptions should have the same name as the smelly one, but with a trailing `!` (`janky_function!/1`); + 1. A version that raises exceptions should have the same name as the "janky" one, but with a trailing `!` (`janky_function!/1`); -2) Another version, without raised exceptions, should have a name identical to the original version (`janky_function/1`) and should return the result wrapped in a tuple. + 2. Another version, without raised exceptions, should have a name identical to the original version (`janky_function/1`) and should return the result wrapped in a tuple. ```elixir defmodule MyModule do def janky_function(value) do if is_integer(value) do - #... - {:ok, "Result..."} + {:ok, "It's an integer"} else - {:error, "invalid argument. Is not integer!"} + {:error, "expected an integer, got: #{inspect(value)}"} end end def janky_function!(value) do case janky_function(value) do {:ok, result} -> result - {:error, message} -> raise RuntimeError, message: message + {:error, message} -> raise(message) end end end ``` -This refactoring gives clients more freedom to decide how to proceed in the event of errors, defining what is exceptional or not in different situations. As shown next, when an error is not exceptional, clients can use specific control-flow structures, such as the `case` statement along with pattern matching. +This refactoring gives users more freedom to decide how to proceed in the event of errors, defining what is exceptional or not in different situations. As shown next, when an error is not exceptional, clients can use specific control-flow structures, such as the `case/2` statement. ```elixir defmodule Client do - # Clients now can also choose to use control-flow structures - # for control-flow when an error is not exceptional. - def foo(arg) do + # Users now can also choose to use control-flow structures + # for control flow when an error is not exceptional. + def print_janky_function(arg) do case MyModule.janky_function(arg) do {:ok, value} -> "All good! #{value}." {:error, reason} -> "Uh oh! #{reason}." @@ -232,10 +229,10 @@ end ``` ```elixir -iex> Client.foo(1) -"All good! Result...." -iex> Client.foo("lucas") -"Uh oh! invalid argument. Is not integer!." +iex> Client.print_janky_function(1) +"All good! It's an integer." +iex> Client.print_janky_function("Lucas") +"Uh oh! expected an integer, got: \"Lucas\"" ``` ## Using application configuration for libraries