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

Exploring Code Smells - ElixirConf US 2023

Elaine Naomi
September 06, 2023

Exploring Code Smells - ElixirConf US 2023

Code smells are indicators of potential design problems in software code. They can impact maintainability, readability, and overall code quality.

While code smells have been extensively studied in object-oriented programming languages, their applicability and identification in functional programming languages like Elixir remain a fascinating area of exploration.

In this talk, we will dive into the world of Elixir and uncover code smells that may arise in its codebase. We will explore the traditional code smells (including the newly cataloged Elixir-specific code smells), discuss their impact on Elixir applications, and provide practical insights into refactoring techniques to eliminate them.

Elaine Naomi

September 06, 2023
Tweet

More Decks by Elaine Naomi

Other Decks in Programming

Transcript

  1. ELAINE NAOMI WATANABE B.Sc. in Computer Engineering M.Sc. in Computer

    Science Senior Software Engineer @ TheRealReal twitter.com/elaine_nw speakerdeck.com/elainenaomi linkedin.com/in/elainenaomi
  2. #

  3. ?

  4. They are symptoms of poor design and implementation choice. Tufano,

    Michele et al. "When and why your code starts to smell bad". 37th IEEE International Conference on Software Engineering (2015): 403-414.
  5. Code smells may slow down development or increase the risk

    of bugs or failures in the future https://en.wikipedia.org/wiki/Code_smell
  6. Mysterious Name Duplicated Code Long Function Long Parameter List Global

    Data Mutable Data Divergent Change Shotgun Surgery Feature Envy Data Clumps Primitive Obsession Repeated Switches Loops Lazy Element Speculative Generality Temporary Field Message Chains Middle Man Insider Trading Large Class Alternative Classes with Different Interfaces Data Class Refused Bequest Comments 2018
  7. CATALOG OF REFACTORINGS Change Function Declaration Change Reference to Value

    Change Value to Reference Collapse Hierarchy Combine Functions into Class Combine Functions into Transform Consolidate Conditional Expression Decompose Conditional Encapsulate Collection Encapsulate Record Encapsulate Variable Extract Class Extract Function Extract Superclass Extract Variable Hide Delegate Inline Class Inline Function Inline Variable Introduce Assertion Introduce Parameter Object Introduce Special Case Move Field Move Function Move Statements into Function Move Statements to Callers Parameterize Function Preserve Whole Object Pull Up Constructor Body Pull Up Field Pull Up Method Push Down Field Push Down Method Remove Dead Code Remove Flag Argument Remove Middle Man Remove Setting Method Remove Subclass Rename Field Rename Variable Replace Command with Function Replace Conditional with Polymorphism Replace Constructor with Factory Function Replace Derived Variable with Query Replace Function with Command Replace Inline Code with Function Call Replace Loop with Pipeline Replace Nested Conditional with Guard Clauses Replace Parameter with Query Replace Primitive with Object Replace Query with Parameter Replace Subclass with Delegate Replace Superclass with Delegate Replace Temp with Query Replace Type Code with Subclasses Separate Query from Modifier Slide Statements Split Loop Split Phase Split Variable Substitute Algorithm https://refactoring.com/catalog/ 2018
  8. defmodule Cart do # ... def exec(i, d) do i

    |> Enum.map(& &1.pr * &1.qt) |> Enum.sum() |> Kernel.-(d) end end
  9. defmodule Cart do # ... def exec(i, d) do i

    |> Enum.map(& &1.pr * &1.qt) |> Enum.sum() |> Kernel.-(d) end end What are these variables?
  10. defmodule Cart do # ... def exec(i, d) do i

    |> Enum.map(& &1.pr * &1.qt) |> Enum.sum() |> Kernel.-(d) end end Rename the variables
  11. defmodule Cart do # ... def exec(items, cart_discount) do items

    |> Enum.map(& &1.pr * &1.qt) |> Enum.sum() |> Kernel.-(cart_discount) end end
  12. defmodule Cart do # ... def exec(items, cart_discount) do items

    |> Enum.map(& &1.pr * &1.qt) |> Enum.sum() |> Kernel.-(cart_discount) end end Run tests
  13. defmodule Cart do # ... def exec(items, cart_discount) do items

    |> Enum.map(& &1.pr * &1.qt) |> Enum.sum() |> Kernel.-(cart_discount) end end What does `exec` mean?
  14. defmodule Cart do # ... def calculate_total(items, cart_discount) do items

    |> Enum.map(& &1.pr * &1.qt) |> Enum.sum() |> Kernel.-(cart_discount) end end
  15. defmodule Cart do # ... def calculate_total(items, cart_discount) do items

    |> Enum.map(& &1.pr * &1.qt) |> Enum.sum() |> Kernel.-(cart_discount) end end
  16. defmodule Cart do # ... def calculate_total(items, cart_discount) do items

    |> Enum.map(& &1.pr * &1.qt) |> Enum.sum() |> Kernel.-(cart_discount) end end Find all old references and update them
  17. defmodule Cart do # ... def calculate_total(items, cart_discount) do items

    |> Enum.map(& &1.pr * &1.qt) |> Enum.sum() |> Kernel.-(cart_discount) end end Run tests
  18. defmodule Cart do # ... def calculate_total(items, cart_discount) do items

    |> Enum.map(& &1.pr * &1.qt) |> Enum.sum() |> Kernel.-(cart_discount) end end
  19. defmodule Cart do # ... def calculate_total(items, cart_discount) do items

    |> Enum.map(& &1.pr * &1.qt) |> Enum.sum() |> Kernel.-(cart_discount) end end
  20. defmodule Cart do # ... def calculate_total(items, cart_discount) do items

    |> Enum.map(& &1.pr * &1.qt) |> Enum.sum() |> Kernel.-(cart_discount) end end Skipping for now :)
  21. #

  22. + Maintainability + Extensibility + Reusability + Code readability +

    Performance + Clarity What are we trying to achieve?
  23. #

  24. defmodule WeatherAnalysis do def evaluate(temperature, unit) do if temperature >

    30 and unit == "C" do "It's hot!" else if temperature <= 30 and temperature > 15 and unit == "C" do "It's not that hot nor that cold!" else if temperature <= 15 and unit == "C" do "It's cold." end end end end end Temperature-based analysis (Celsius only)
  25. Temperature-based analysis (Celsius only) defmodule WeatherAnalysis do def evaluate(temperature, unit)

    do if temperature > 30 and unit == "C" do "It's hot!" else if temperature <= 30 and temperature > 15 and unit == "C" do "It's not that hot nor that cold!" else if temperature <= 15 and unit == "C" do "It's cold." end end end end end
  26. defmodule WeatherAnalysis do def evaluate(temperature, unit) do if (temperature >

    30 and unit == "C") or (temperature > 89.6 and unit == "F") do "It's hot!" else if (temperature <= 30 and temperature > 15 and unit == "C") or (temperature <= 89.6 and temperature > 59 and unit == "F") do "It's not that hot nor that cold!" else if (temperature <= 15 and unit == "C") or (temperature <= 59 and unit == "F") do "It's cold." end end end end end Temperature-based analysis (Celsius and Fahrenheit)
  27. defmodule WeatherAnalysis do def evaluate(temperature, unit) do if (temperature >

    30 and unit == "C") or (temperature > 89.6 and unit == "F") do "It's hot!" else if (temperature <= 30 and temperature > 15 and unit == "C") or (temperature <= 89.6 and temperature > 59 and unit == "F") do "It's not that hot nor that cold!" else if (temperature <= 15 and unit == "C") or (temperature <= 59 and unit == "F") do "It's cold." end end end end end Temperature-based analysis (Celsius and Fahrenheit)
  28. Temperature-based analysis (Celsius and Fahrenheit) defmodule WeatherAnalysis do def evaluate(temperature,

    unit) do if (temperature > 30 and unit == "C") or (temperature > 89.6 and unit == "F") do "It's hot!" else if (temperature <= 30 and temperature > 15 and unit == "C") or (temperature <= 89.6 and temperature > 59 and unit == "F") do "It's not that hot nor that cold!" else if (temperature <= 15 and unit == "C") or (temperature <= 59 and unit == "F") do "It's cold." end end end end end
  29. defmodule WeatherAnalysis do def evaluate(temperature, "C") when temperature > 30,

    do: "It's hot!" def evaluate(temperature, "F") when temperature > 89.6, do: "It's hot!" def evaluate(temperature, "C") when temperature <= 30 and temperature > 15, do: "It's not that hot nor that cold!" def evaluate(temperature, "F") when temperature <= 89.6 and temperature > 59, do: "It's not that hot nor that cold!" def evaluate(temperature, "C") when temperature <= 15, do: "It's cold." def evaluate(temperature, "F") when temperature <= 59, do: "It's cold." end
  30. defmodule WeatherAnalysis do def evaluate(temperature, "C") when temperature > 30,

    do: "It's hot!" def evaluate(temperature, "F") when temperature > 89.6, do: "It's hot!" def evaluate(temperature, "C") when temperature <= 30 and temperature > 15, do: "It's not that hot nor that cold!" def evaluate(temperature, "F") when temperature <= 89.6 and temperature > 59, do: "It's not that hot nor that cold!" def evaluate(temperature, "C") when temperature <= 15, do: "It's cold." def evaluate(temperature, "F") when temperature <= 59, do: "It's cold." end
  31. Refactorings Extract Function, Slide Statements, Pull Up Method DRY, Single

    Responsibility Principle Code Smell Duplicated code
  32. #

  33. defmodule WeatherAnalysis do def evaluate(temperature, "C") when temperature > 30,

    do: "It's hot!" def evaluate(temperature, "F") when temperature > 89.6, do: "It's hot!" def evaluate(temperature, "C") when temperature <= 30 and temperature > 15, do: "It's not that hot nor that cold!" def evaluate(temperature, "F") when temperature <= 89.6 and temperature > 59, do: "It's not that hot nor that cold!" def evaluate(temperature, "C") when temperature <= 15, do: "It's cold." def evaluate(temperature, "F") when temperature <= 59, do: "It's cold." end
  34. defmodule WeatherAnalysis do def evaluate(temperature, "C") when temperature > 30,

    do: "It's hot!" def evaluate(temperature, "F") when temperature > 89.6, do: "It's hot!" def evaluate(temperature, "C") when temperature <= 30 and temperature > 15, do: "It's not that hot nor that cold!" def evaluate(temperature, "F") when temperature <= 89.6 and temperature > 59, do: "It's not that hot nor that cold!" def evaluate(temperature, "C") when temperature <= 15, do: "It's cold." def evaluate(temperature, "F") when temperature <= 59, do: "It's cold." end
  35. defmodule WeatherAnalysis do # ... defp parse(temperature, "C"), do: temperature

    defp parse(temperature, "F"), do: (temperature - 32) * 5 / 9 # ... end
  36. defmodule WeatherAnalysis do # ... defp do_evaluate(temperature) when temperature >

    30, do: "It's really hot!" defp do_evaluate(temperature) when temperature <= 30 and temperature > 15, do: "It's not that hot nor that cold!" defp do_evaluate(temperature) when temperature <= 15, do: "It's cold." end
  37. defmodule ShoppingCart do # Rule 1 def calculate_total(items, subscription) do

    # ... end # Rule 2 def calculate_shipping(zip_code, %{id: 3}), do: 0.0 def calculate_shipping(zip_code, %{id: 4}), do: 0.0 def calculate_shipping(zip_code, _) do 10.0 * Location.calculate(zip_code) end # Rule 3 def apply_discount(total, %{id: 3}), do: total * 0.9 def apply_discount(total, %{id: 4}), do: total * 0.9 def apply_discount(total, _), do: total # Rule 4 def send_message_subscription(%{id: 3}, _), do: nil def send_message_subscription(%{id: 4}, _), do: nil def send_message_subscription(subscription, user), do: Subscription.send_email_upgrade(subscription, user) # Rule 5 def print(user, order) do # ... end end
  38. Rule #1 - Calculate Total defmodule ShoppingCart do # Rule

    1 def calculate_total(items, subscription) do # ... end # Rule 2 def calculate_shipping(zip_code, %{id: 3}), do: 0.0 def calculate_shipping(zip_code, %{id: 4}), do: 0.0 def calculate_shipping(zip_code, _) do 10.0 * Location.calculate(zip_code) end # Rule 3 def apply_discount(total, %{id: 3}), do: total * 0.9 def apply_discount(total, %{id: 4}), do: total * 0.9 def apply_discount(total, _), do: total # Rule 4 def send_message_subscription(%{id: 3}, _), do: nil def send_message_subscription(%{id: 4}, _), do: nil def send_message_subscription(subscription, user), do: Subscription.send_email_upgrade(subscription, user) # Rule 5 def print(user, order) do # ... end end Rule #2 - Calculate Shipping Taxes Rule #3 - Apply Discount Rule #4 - Send Subscription Notification Rule #5 - Print Order
  39. Hard to read Hard to explain what the module does

    Multiple business rules defmodule ShoppingCart do # Rule 1 def calculate_total(items, subscription) do # ... end # Rule 2 def calculate_shipping(zip_code, %{id: 3}), do: 0.0 def calculate_shipping(zip_code, %{id: 4}), do: 0.0 def calculate_shipping(zip_code, _) do 10.0 * Location.calculate(zip_code) end # Rule 3 def apply_discount(total, %{id: 3}), do: total * 0.9 def apply_discount(total, %{id: 4}), do: total * 0.9 def apply_discount(total, _), do: total # Rule 4 def send_message_subscription(%{id: 3}, _), do: nil def send_message_subscription(%{id: 4}, _), do: nil def send_message_subscription(subscription, user), do: Subscription.send_email_upgrade(subscription, user) # Rule 5 def print(user, order) do # ... end end
  40. defmodule ShoppingCart do # Rule 1 def calculate_total(items, subscription) do

    # ... end # Rule 2 def calculate_shipping(zip_code, %{id: 3}), do: 0.0 def calculate_shipping(zip_code, %{id: 4}), do: 0.0 def calculate_shipping(zip_code, _) do 10.0 * Location.calculate(zip_code) end # Rule 3 def apply_discount(total, %{id: 3}), do: total * 0.9 def apply_discount(total, %{id: 4}), do: total * 0.9 def apply_discount(total, _), do: total # Rule 4 def send_message_subscription(%{id: 3}, _), do: nil def send_message_subscription(%{id: 4}, _), do: nil def send_message_subscription(subscription, user), do: Subscription.send_email_upgrade(subscription, user) # Rule 5 def print(user, order) do # ... end end
  41. defmodule ShoppingCart do # Rule 1 def calculate_total(items, subscription) do

    # ... end # Rule 2 def calculate_shipping(zip_code, %{id: 3}), do: 0.0 def calculate_shipping(zip_code, %{id: 4}), do: 0.0 def calculate_shipping(zip_code, _) do 10.0 * Location.calculate(zip_code) end # Rule 3 def apply_discount(total, %{id: 3}), do: total * 0.9 def apply_discount(total, %{id: 4}), do: total * 0.9 def apply_discount(total, _), do: total # Rule 4 def send_message_subscription(%{id: 3}, _), do: nil def send_message_subscription(%{id: 4}, _), do: nil def send_message_subscription(subscription, user), do: Subscription.send_email_upgrade(subscription, user) # Rule 5 def print(user, order) do # ... end end defmodule UserNotification do def send_message_subscription(%{id: 3}, _), do: nil def send_message_subscription(%{id: 4}, _), do: nil def send_message_subscription(subscription, user) do Subscription.send_email_upgrade(subscription, user) end end Extract module
  42. defmodule ShoppingCart do # Rule 1 def calculate_total(items, subscription) do

    # ... end # Rule 2 def calculate_shipping(zip_code, %{id: 3}), do: 0.0 def calculate_shipping(zip_code, %{id: 4}), do: 0.0 def calculate_shipping(zip_code, _) do 10.0 * Location.calculate(zip_code) end # Rule 3 def apply_discount(total, %{id: 3}), do: total * 0.9 def apply_discount(total, %{id: 4}), do: total * 0.9 def apply_discount(total, _), do: total # Rule 4 def send_message_subscription(%{id: 3}, _), do: nil def send_message_subscription(%{id: 4}, _), do: nil def send_message_subscription(subscription, user), do: Subscription.send_email_upgrade(subscription, user) # Rule 5 def print(user, order) do # ... end end Before: #5 Rules
  43. defmodule ShoppingCart do # Rule 1 def calculate_total(items, subscription) do

    # ... end # Rule 2 def calculate_shipping(zip_code, %{id: 3}), do: 0.0 def calculate_shipping(zip_code, %{id: 4}), do: 0.0 def calculate_shipping(zip_code, _) do 10.0 * Location.calculate(zip_code) end # Rule 3 def apply_discount(total, %{id: 3}), do: total * 0.9 def apply_discount(total, %{id: 4}), do: total * 0.9 def apply_discount(total, _), do: total # Rule 5 def print(user, order) do # ... end end After: #4 Rules
  44. Refactorings Replace Temp with Query, Introduce Parameter Object, Preserve Whole

    Object, Split Loop, Replace Function with Command, Decompose Conditional, Replace Conditional with Polymorphism Code Smell Long Function
  45. #

  46. #

  47. Refactorings Move Function, Move Field, Combine Functions into Class, Combine

    Functions into Transform, Split Phase, Inline Function, Inline Class Code Smell Shotgun Surgery
  48. defmodule ShoppingCart do def calculate_shipping(_zip_code, %{id: 3}), do: 0.0 def

    calculate_shipping(_zip_code, %{id: 4}), do: 0.0 def calculate_shipping(zip_code, _), do: 10.0 * Location.calculate(zip_code) def apply_discount(total, %{id: 3}), do: total * 0.95 def apply_discount(total, %{id: 4}), do: total * 0.9 def apply_discount(total, _), do: total end
  49. defmodule ShoppingCart do def calculate_shipping(_zip_code, %{id: 3}), do: 0.0 def

    calculate_shipping(_zip_code, %{id: 4}), do: 0.0 def calculate_shipping(zip_code, _), do: 10.0 * Location.calculate(zip_code) def apply_discount(total, %{id: 3}), do: total * 0.95 def apply_discount(total, %{id: 4}), do: total * 0.9 def apply_discount(total, _), do: total end Enum.member?([3, 4], subscription.id)
  50. defmodule ShoppingCart do def calculate_shipping(_zip_code, %{id: 3}), do: 0.0 def

    calculate_shipping(_zip_code, %{id: 4}), do: 0.0 def calculate_shipping(zip_code, _), do: 10.0 * Location.calculate(zip_code) def apply_discount(total, %{id: 3}), do: total * 0.95 def apply_discount(total, %{id: 4}), do: total * 0.9 def apply_discount(total, _), do: total end Enum.member?([3, 4], subscription.id)
  51. defmodule ShoppingCart do def calculate_shipping(_zip_code, %{id: 3}), do: 0.0 def

    calculate_shipping(_zip_code, %{id: 4}), do: 0.0 def calculate_shipping(zip_code, _), do: 10.0 * Location.calculate(zip_code) def apply_discount(total, %{id: 3}), do: total * 0.95 def apply_discount(total, %{id: 4}), do: total * 0.9 def apply_discount(total, _), do: total end Enum.member?([3, 4], subscription.id)
  52. defmodule ShoppingCart do def calculate_shipping(_zip_code, %{id: 3}), do: 0.0 def

    calculate_shipping(_zip_code, %{id: 4}), do: 0.0 def calculate_shipping(_zip_code, %{id: 8}), do: 0.0 def calculate_shipping(zip_code, _), do: 10.0 * Location.calculate(zip_code) def apply_discount(total, %{id: 3}), do: total * 0.95 def apply_discount(total, %{id: 4}), do: total * 0.9 def apply_discount(total, %{id: 8}), do: total * 0.85 def apply_discount(total, _), do: total end Enum.member?([3, 4, 8], subscription.id)
  53. defmodule SubscriptionRenewal do def execute(%{id: id}, user) do case id

    do 3 -> Billing.perform(user, id, 90.0) 4 -> Billing.perform(user, id, 95.0) 8 -> Billing.perform(user, id, 100.0) end end end Another small change after global search
  54. defmodule SubscriptionRenewal do def execute(%{id: id}, user) do case id

    do 3 -> Billing.perform(user, id, 90.0) 4 -> Billing.perform(user, id, 95.0) 8 -> Billing.perform(user, id, 100.0) end end end Another small change after global search
  55. defmodule ShoppingCart do def calculate_shipping(_zip_code, %{id: 3}), do: 0.0 def

    calculate_shipping(_zip_code, %{id: 4}), do: 0.0 def calculate_shipping(_zip_code, %{id: 8}), do: 0.0 def calculate_shipping(zip_code, _), do: 10.0 * Location.calculate(zip_code) def apply_discount(total, %{id: 3}), do: total * 0.95 def apply_discount(total, %{id: 4}), do: total * 0.9 def apply_discount(total, %{id: 8}), do: total * 0.85 def apply_discount(total, _), do: total end Enum.member?([3, 4, 8], subscription.id) Shipping Discount
  56. defmodule SubscriptionRenewal do def execute(%{id: id}, user) do case id

    do 3 -> Billing.perform(user, id, 90.0) 4 -> Billing.perform(user, id, 95.0) 8 -> Billing.perform(user, id, 100.0) end end end Renewal
  57. defmodule Subscription do defstruct [:id, :price, :shipping_tax, :discount_rate] def available_subscriptions

    do [ %Subscription{id: 3, price: 90.0, shipping_tax: 0.0, discount_rate: 0.95}, %Subscription{id: 4, price: 95.0, shipping_tax: 0.0, discount_rate: 0.9}, %Subscription{id: 8, price: 100.0, shipping_tax: 0.0, discount_rate: 0.85} ] end end
  58. defmodule Subscription do defstruct [:id, :price, :shipping_tax, :discount_rate] def available_subscriptions

    do [ %Subscription{id: 3, price: 90.0, shipping_tax: 0.0, discount_rate: 0.95}, %Subscription{id: 4, price: 95.0, shipping_tax: 0.0, discount_rate: 0.9}, %Subscription{id: 8, price: 100.0, shipping_tax: 0.0, discount_rate: 0.85} ] end end
  59. defmodule SubscriptionRenewal do def execute(%{id: id}, user) do case id

    do 3 -> Billing.perform(user, id, 90.0) 4 -> Billing.perform(user, id, 95.0) 8 -> Billing.perform(user, id, 100.0) end end end
  60. defmodule ShoppingCart do def calculate_shipping(_zip_code, %{id: 3}), do: 0.0 def

    calculate_shipping(_zip_code, %{id: 4}), do: 0.0 def calculate_shipping(_zip_code, %{id: 8}), do: 0.0 def calculate_shipping(zip_code, _), do: 10.0 * Location.calculate(zip_code) def apply_discount(total, %{id: 3}), do: total * 0.95 def apply_discount(total, %{id: 4}), do: total * 0.9 def apply_discount(total, %{id: 8}), do: total * 0.85 def apply_discount(total, _), do: total end Enum.member?([3, 4, 8], subscription.id)
  61. defmodule ShoppingCart do # ... def calculate_shipping(_, %Subscription{shipping_tax: shipping_tax}), do:

    shipping_tax def calculate_shipping(zip_code, _), do: 10.0 * Location.calculate(zip_code) def apply_discount(total, %Subscription{discount_rate: discount_rate}), do: total * discount_rate def apply_discount(total, _), do: total end
  62. #

  63. These examples are for illustrative purposes Be mindful about issues

    when using float for monetary calculations (not only in Elixir!)
  64. https://www.smbc-comics.com/?id=2999 0.1 + 0.2 == 0.3 0.1 + 0.2 ==

    0.30000000000000004 > false > true https://0.30000000000000004.com/ Unexpected loss of precision during rounding
  65. #

  66. Loops Lazy Element Speculative Generality Temporary Field Message Chains Middle

    Man Insider Trading Large Class Alternative Classes with Different Interfaces Data Class Refused Bequest Comments Mysterious Name Duplicated Code Long Function Long Parameter List Global Data Mutable Data Divergent Change Shotgun Surgery Feature Envy Data Clumps Primitive Obsession Repeated Switches 2018
  67. Prof. Marco Tulio Valente Prof. Lucas Francisco da Matta Vegi

    Federal University of Minas Gerais (UFMG) Belo Horizonte, Minas Gerais, Brazil
  68. #

  69. Multi-clause functions in Elixir Multi-clause functions to group unrelated functionalities

    Code Smell Unrelated multi-clause function = Code smell != Code smell
  70. def update(%User{} = user, new_email) when new_email != nil do

    %{user | email: new_email} end def update(%User{} = user, _new_email), do: user def update(%User.PaymentMethod{} = pay_method, default?) do %{pay_method | default: default?} end def update(%User.Notification{} = notification, channel) do %{notification | channel: channel} end
  71. def update(%User{} = user, new_email) when new_email != nil do

    %{user | email: new_email} end def update(%User{} = user, _new_email), do: user def update(%User.PaymentMethod{} = pay_method, default?) do %{pay_method | default: default?} end def update(%User.Notification{} = notification, channel) do %{notification | channel: channel} end
  72. def update(%User{} = user, new_email) when new_email != nil do

    %{user | email: new_email} end def update(%User{} = user, _new_email), do: user def update(%User.PaymentMethod{} = pay_method, default?) do %{pay_method | default: default?} end def update(%User.Notification{} = notification, channel) do %{notification | channel: channel} end #3 #2 #1
  73. def update(%User{} = user, new_email) when new_email != nil do

    %{user | email: new_email} end def update(%User{} = user, _new_email), do: user def update(%User.PaymentMethod{} = pay_method, default?) do %{pay_method | default: default?} end def update(%User.Notification{} = notification, channel) do %{notification | channel: channel} end #1
  74. @doc """ Updates a user's email address. """ def update_user_email(%User{}

    = user, new_email) when new_email != nil do %{user | email: new_email} end def update_user_email(%User{} = user, _new_email), do: user #1 New function
  75. def update(%User{} = user, new_email) when new_email != nil do

    %{user | email: new_email} end def update(%User{} = user, _new_email), do: user def update(%User.PaymentMethod{} = pay_method, default?) do %{pay_method | default: default?} end def update(%User.Notification{} = notification, channel) do %{notification | channel: channel} end #2
  76. @doc """ Updates user's default payment method. """ def update_user_payment_method(%User.PaymentMethod{}

    = pay_method, default?) do %{pay_method | default: default?} end #2 New function
  77. def update(%User{} = user, new_email) when new_email != nil do

    %{user | email: new_email} end def update(%User{} = user, _new_email), do: user def update(%User.PaymentMethod{} = pay_method, default?) do %{pay_method | default: default?} end def update(%User.Notification{} = notification, channel) do %{notification | channel: channel} end #3
  78. @doc """ Updates a user's notification preferences. """ def update_user_notification(%User.Notification{}

    = notification, channel) do %{notification | channel: channel} end #3 New function
  79. Code Smell Compile-time global configuration Module attributes Defined at compile-time

    Application Environment May not be available in memory during compile-time/build-time *warnings or errors can be triggered by Elixir
  80. defmodule DashSplitter do @parts Application.fetch_env!(:app_config, :parts) def split(string) when is_binary(string)

    do String.split(string, "-", parts: @parts) end end ** (ArgumentError) could not fetch application environment :parts for application :app_config because the application was not loaded nor configured
  81. defmodule DashSplitter do @parts Application.compile_env(:app_config, :parts, 3) def split(string) when

    is_binary(string) do String.split(string, "-", parts: @parts) end end *with default
  82. #

  83. `with` statement with a single complex `else` block Code Smell

    Complex else clauses in with = Code smell `with` statement + else != Code smell
  84. def open_decoded_file(path) do with {:ok, encoded} <- File.read(path), {:ok, value}

    <- Base.decode64(encoded) do value else {:error, _} -> :badfile :error -> :badencoding end end Code Smell Complex else clauses in with
  85. def open_decoded_file(path) do with {:ok, encoded} <- File.read(path), {:ok, value}

    <- Base.decode64(encoded) do value else {:error, _} -> :badfile :error -> :badencoding end end Code Smell Complex else clauses in with Difficult to know from which clause the error value came.
  86. def open_decoded_file(path) do with {:ok, encoded} <- File.read(path), {:ok, value}

    <- Base.decode64(encoded) do value else {:error, _} -> :badfile :error -> :badencoding end end Code Smell Complex else clauses in with
  87. Code Smell Complex else clauses in with def open_decoded_file(path) do

    with {:ok, encoded} <- file_read(path), {:ok, value} <- base_decode64(encoded) do value end end
  88. Code Smell Complex else clauses in with def open_decoded_file(path) do

    with {:ok, encoded} <- file_read(path), {:ok, value} <- base_decode64(encoded) do value end end
  89. defp file_read(path) do case File.read(path) do {:ok, contents} -> {:ok,

    contents} {:error, _} -> :badfile end end Code Smell Complex else clauses in with
  90. defp file_read(path) do case File.read(path) do {:ok, contents} -> {:ok,

    contents} {:error, _} -> :badfile end end Code Smell Complex else clauses in with
  91. Code Smell Complex else clauses in with def open_decoded_file(path) do

    with {:ok, encoded} <- file_read(path), {:ok, value} <- base_decode64(encoded) do value end end
  92. Code Smell Complex else clauses in with defp base_decode64(contents) do

    case Base.decode64(contents) do {:ok, contents} -> {:ok, contents} :error -> :badencoding end end
  93. Code Smell Complex else clauses in with defp base_decode64(contents) do

    case Base.decode64(contents) do {:ok, contents} -> {:ok, contents} :error -> :badencoding end end
  94. Creating atoms dynamically with no control Code Smell Complex else

    clauses in with = Code smell Creating atoms dynamically != Code smell *Atoms are constants that are not garbage collected by the Erlang VM
  95. Anti-pattern structure Name Unique, To facilitate communication Problem How it

    can harm code quality What impacts it can have for developers Example Code + textual description Refactoring Suggested changes to improve code quality
  96. Anti-pattern structure Name Unique, To facilitate communication Problem How it

    can harm code quality What impacts it can have for developers Example Code + textual description Refactoring Suggested changes to improve code quality
  97. Research indicates that almost 60% of programmers’ time is spent

    understanding code, rather than writing code. From The Programmer's Brain - Felienne Hermans
  98. #