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

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. ElixirConf US 2023
    EXPLORING
    CODE SMELLS

    View Slide

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

    View Slide

  3. View Slide

  4. The RealReal - Elixir Adoption Story
    youtube.com/watch?v=sTs_4T1ufLY

    View Slide

  5. CODE SMELLS

    View Slide

  6. View Slide

  7. https://ieeexplore.ieee.org/document/6671299
    2013

    View Slide

  8. https://ieeexplore.ieee.org/document/6671299
    2013

    View Slide

  9. Do Developers Care about Code Smells?
    2013

    View Slide

  10. Do Developers Care about Code Smells?
    Concerns about code smells
    2013

    View Slide

  11. Do Developers Care about Code Smells?
    Concerns about code smells
    2013

    View Slide

  12. Do Developers Care about Code Smells?
    52%
    Concerns about code smells
    2013

    View Slide

  13. What are their concerns
    about code smells?

    View Slide

  14. Developer productivity
    Product evolvability
    Quality of end-product
    Self-improvement
    etc
    What are their concerns
    about code smells?

    View Slide

  15. Do Developers Care about Code Smells?
    52%
    Concerns about code smells
    2013

    View Slide

  16. Do Developers Care about Code Smells?
    Concerns about code smells
    2013
    26%

    View Slide

  17. Lack of awareness about code smells No concerns

    View Slide

  18. 2013
    2013

    View Slide

  19. Knowledge about code smells
    2013
    2013

    View Slide

  20. Knowledge about code smells
    2013
    2013
    58%

    View Slide

  21. #

    View Slide

  22. TERMINOLOGY

    View Slide

  23. https://martinfowler.com/bliki/CodeSmell.html
    https://en.wikipedia.org/wiki/Code_smell
    CODE (BAD) SMELLS
    Any characteristic in the source code that may
    indicate a deeper quality problem in the system

    View Slide

  24. ?

    View Slide

  25. ?
    Nope

    View Slide

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

    View Slide

  27. https://martinfowler.com/bliki/DesignStaminaHypothesis.html
    design payoff line
    no design
    good design
    cumulative
    functionality
    time

    View Slide

  28. Code smells may slow down development or
    increase the risk of bugs or failures in the future
    https://en.wikipedia.org/wiki/Code_smell

    View Slide

  29. 1999
    22
    code smells

    View Slide

  30. 2018
    18+
    years later
    24
    code smells

    View Slide

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

    View Slide

  32. And how to fix code smells?

    View Slide

  33. 1999
    22
    code smells

    View Slide

  34. 22
    code smells
    72
    refactoring
    techniques
    1999

    View Slide

  35. 2018
    18+
    years later
    24
    code smells
    61
    refactoring
    techniques

    View Slide

  36. 2018
    18+
    years later
    24
    code smells
    61
    refactoring
    techniques

    View Slide

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

    View Slide

  38. REFACTORING

    View Slide

  39. View Slide

  40. View Slide

  41. View Slide

  42. View Slide

  43. View Slide

  44. View Slide

  45. CI is failing for weeks…

    View Slide

  46. Hard to test, hard to deliver…

    View Slide

  47. View Slide

  48. And, occasionally …

    View Slide

  49. This is NOT refactoring

    View Slide

  50. https://martinfowler.com/books/refactoring.html
    https://en.wikipedia.org/wiki/Code_refactoring
    REFACTORING
    A controlled technique for improving the design
    of an existing code base.

    View Slide

  51. Small behavior-preserving transformations

    View Slide

  52. Small behavior-preserving transformations
    *about the observable behavior

    View Slide

  53. Identify a code
    smell
    To fix
    now?
    Apply Refactoring
    Run tests
    Yes
    Next
    No
    Finish

    View Slide

  54. View Slide

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

    View Slide

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

    View Slide

  57. Code Smell: "Mysterious Name"
    Also called "Bad Naming"

    View Slide

  58. Refactoring: "Rename Variable"

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  63. Code Smell: "Mysterious Name"

    View Slide

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

    View Slide

  65. Refactoring: "Change Function Declaration"

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  70. Code Smell: "Mysterious Name"

    View Slide

  71. Refactoring: "Rename Field"

    View Slide

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

    View Slide

  73. 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 :)

    View Slide

  74. #

    View Slide

  75. What are we trying
    to achieve?

    View Slide

  76. + Maintainability
    + Extensibility
    + Reusability
    + Code readability
    + Performance
    + Clarity
    What are we trying
    to achieve?

    View Slide

  77. #

    View Slide

  78. Finding code smells

    View Slide

  79. View Slide

  80. 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)

    View Slide

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

    View Slide

  82. 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)

    View Slide

  83. 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)

    View Slide

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

    View Slide

  85. View Slide

  86. Let's use pattern matching!

    View Slide

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

    View Slide

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

    View Slide

  89. View Slide

  90. In both examples:
    Duplicated code for Fahrenheit and Celsius
    Many execution paths

    View Slide

  91. TRADITIONAL CODE SMELLS
    IN ELIXIR CONTEXT

    View Slide

  92. Code Smell: "Duplicated Code"

    View Slide

  93. Refactorings Extract Function,
    Slide Statements,
    Pull Up Method
    Code Smell Duplicated code

    View Slide

  94. Refactorings Extract Function,
    Slide Statements,
    Pull Up Method
    DRY, Single Responsibility Principle
    Code Smell Duplicated code

    View Slide

  95. #

    View Slide

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

    View Slide

  97. Refactoring: Extract Function

    View Slide

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

    View Slide

  99. defmodule WeatherAnalysis do
    def evaluate(temperature, unit) do
    temperature
    |> parse(unit)
    |> do_evaluate()
    end
    # ...
    end

    View Slide

  100. defmodule WeatherAnalysis do
    def evaluate(temperature, unit) do
    temperature
    |> parse(unit)
    |> do_evaluate()
    end
    # ...
    end

    View Slide

  101. defmodule WeatherAnalysis do
    # ...
    defp parse(temperature, "C"),
    do: temperature
    defp parse(temperature, "F"),
    do: (temperature - 32) * 5 / 9
    # ...
    end

    View Slide

  102. defmodule WeatherAnalysis do
    def evaluate(temperature, unit) do
    temperature
    |> parse(unit)
    |> do_evaluate()
    end
    # ...
    end

    View Slide

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

    View Slide

  104. Code Smell: "Large Class"
    * Large Module

    View Slide

  105. Refactorings Extract Class, Extra Superclass,
    Replace Type Code with Subclasses
    Code Smell Large Class

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  109. Refactoring: "Extract Class"

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  114. Code Smell: "Long Function"

    View Slide

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

    View Slide

  116. #

    View Slide

  117. View Slide

  118. About cognition process in
    programming activities

    View Slide

  119. #

    View Slide

  120. Small change across several different files
    Code Smell: "Shotgun Surgery"

    View Slide

  121. Refactorings Move Function, Move Field,
    Combine Functions into Class,
    Combine Functions into Transform,
    Split Phase, Inline Function,
    Inline Class
    Code Smell Shotgun Surgery

    View Slide

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

    View Slide

  123. 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)

    View Slide

  124. 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)

    View Slide

  125. 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)

    View Slide

  126. 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)

    View Slide

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

    View Slide

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

    View Slide

  129. Refactoring: "Move Field"

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  135. defmodule SubscriptionRenewal do
    # ...
    def execute(subscription, user) do
    Billing.perform(user, subscription.id, subscription.price)
    end
    end

    View Slide

  136. 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)

    View Slide

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

    View Slide

  138. #

    View Slide

  139. These examples are for illustrative purposes
    Be mindful about issues when
    using float for monetary calculations
    (not only in Elixir!)

    View Slide

  140. https://hexdocs.pm/elixir/1.15.5/Float.html

    View Slide

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

    View Slide

  142. #

    View Slide

  143. Not all of the traditional code smells are
    applicable to Elixir context

    View Slide

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

    View Slide

  145. https://doi.org/10.1007/s10664-023-10343-6

    View Slide

  146. Prof. Marco Tulio Valente
    Prof. Lucas Francisco da
    Matta Vegi
    Federal University of Minas Gerais (UFMG)
    Belo Horizonte, Minas Gerais, Brazil

    View Slide

  147. ELIXIR CODE SMELLS

    View Slide

  148. About sub-optimal code structures that can
    harm the internal quality of Elixir systems

    View Slide

  149. 14 Design-related smells
    09 Low-level concerns smells

    View Slide

  150. https://doi.org/10.1007/s10664-023-10343-6

    View Slide

  151. https://github.com/lucasvegi/Elixir-Code-Smells

    View Slide

  152. #

    View Slide

  153. Design-related smells

    View Slide

  154. Code Smell: "Unrelated multi-clause function"

    View Slide

  155. Multi-clause functions in Elixir
    Multi-clause functions to
    group unrelated functionalities
    Code Smell Unrelated multi-clause function
    = Code smell
    != Code smell

    View Slide

  156. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  160. Refactoring

    View Slide

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

    View Slide

  162. @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

    View Slide

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

    View Slide

  164. @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

    View Slide

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

    View Slide

  166. @doc """
    Updates a user's notification preferences.
    """
    def update_user_notification(%User.Notification{} = notification,
    channel) do
    %{notification | channel: channel}
    end
    #3
    New function

    View Slide

  167. Code Smell:
    "Compile-time global configuration"

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  172. Compile time vs Runtime
    https://elixir-lang.org/getting-started/mix-otp/config-and-releases.html#configuration

    View Slide

  173. #

    View Slide

  174. Low-level concerns smells

    View Slide

  175. Code Smell: "Complex else clauses in with"

    View Slide

  176. `with` statement with
    a single complex `else` block
    Code Smell Complex else clauses in with
    = Code smell
    `with` statement + else != Code smell

    View Slide

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

    View Slide

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

    View Slide

  179. Refactoring

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  188. Code Smell: "Dynamic Atom Creation"

    View Slide

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

    View Slide

  190. defmodule Identifier do
    def generate(id) when is_bitstring(id) do
    String.to_atom(id) #<= dynamic atom creation!!
    end
    end

    View Slide

  191. defmodule Identifier do
    def generate(id) when is_bitstring(id) do
    String.to_atom(id) #<= dynamic atom creation!!
    end
    end

    View Slide

  192. Refactoring

    View Slide

  193. defmodule Identifier do
    def generate(id) when is_bitstring(id) do
    String.to_atom(id) #<= dynamic atom creation!!
    end
    end

    View Slide

  194. defmodule Identifier do
    def generate(id) when is_bitstring(id) do
    String.to_existing_atom(id) #<= maps a string to an existing atom!
    end
    end

    View Slide

  195. Elixir 1.16

    View Slide

  196. https://hexdocs.pm/elixir/main/what-anti-patterns.html

    View Slide

  197. Code-related anti-patterns
    Design-related anti-patterns
    Process-related anti-patterns
    Meta-programming anti-patterns

    View Slide

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

    View Slide

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

    View Slide

  200. View Slide

  201. WHAT'S NEXT?

    View Slide

  202. #1 - Catalog of Refactorings
    for Elixir Code Smells

    View Slide

  203. https://homepages.dcc.ufmg.br/~mtov/pub/2023-icsme-nier-elixir.pdf
    2023

    View Slide

  204. #2 - Tooling

    View Slide

  205. hexdocs.pm/credo
    Improving

    View Slide

  206. A.I.?
    GH Copilot
    ChatGPT
    etc.

    View Slide

  207. #3 - Metrics?

    View Slide

  208. Cyclomatic complexity

    View Slide

  209. View Slide

  210. ABC Software Metric

    View Slide

  211. View Slide

  212. WRAPPING UP

    View Slide

  213. History
    Traditional and Elixir code smells
    Impact of code smells over time
    Refactoring techniques

    View Slide

  214. # Understability and changeability matter

    View Slide

  215. Research indicates that almost 60% of programmers’ time is spent
    understanding code, rather than writing code.
    From The Programmer's Brain - Felienne Hermans

    View Slide

  216. # Automated tests + Refactoring =

    View Slide

  217. https://martinfowler.com/bliki/TestCoverage.html

    View Slide

  218. https://martinfowler.com/articles/practical-test-pyramid.html

    View Slide

  219. # Refactoring != Revolution

    View Slide

  220. It's all about accumulative improvements

    View Slide

  221. #

    View Slide

  222. Legacy code in Elixir

    View Slide

  223. How is your Elixir codebase?

    View Slide

  224. Is it EASY TO CHANGE?

    View Slide

  225. speakerdeck.com/elainenaomi
    elainenaomi.dev
    Muito
    obrigada
    illustrations from undraw.co
    translation: thank you so much!

    View Slide

  226. railsgirls.com.br

    View Slide

  227. 2018
    2020
    2021
    2019 2020
    2018

    View Slide

  228. 2022

    View Slide

  229. View Slide

  230. CARNIVAL EDITION
    ELIXIR

    View Slide

  231. View Slide