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

Code Smells in Elixir - CODE BEAM EU 2023

Code Smells in Elixir - CODE BEAM EU 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

October 19, 2023
Tweet

More Decks by Elaine Naomi

Other Decks in Programming

Transcript

  1. ELAINE NAOMI WATANABE twitter.com/elaine_nw speakerdeck.com/elainenaomi linkedin.com/in/elainenaomi B.Sc. in Computer Engineering

    M.Sc. in Computer Science Senior Software Engineer @ TheRealReal Yesterday 11°C Berlin Germany GMT+2 Last Saturday 23°C São Paulo Brazil GMT-3
  2. Code smells are sub-optimal design choices that can degrade different

    aspects of code quality Yamashita, Aiko, and Steve Counsell. "Code smells as system-level indicators of maintainability: An empirical study." Journal of Systems and Software 86.10 (2013): 2639-2653.
  3. They are indicators of software design flaws that could potentially

    affect maintenance Yamashita, Aiko, and Steve Counsell. "Code smells as system-level indicators of maintainability: An empirical study." Journal of Systems and Software 86.10 (2013): 2639-2653.
  4. Alternative Classes with Different Interfaces Loops Lazy Element Speculative Generality

    Temporary Field Message Chains Middle Man Insider Trading Large Class 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
  5. CATALOG OF REFACTORINGS 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 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 https://refactoring.com/catalog/ 2018
  6. Prof. Marco Tulio Valente Prof. Lucas Francisco da Matta Vegi

    Federal University of Minas Gerais (UFMG) Belo Horizonte, Minas Gerais, Brazil
  7. Elixir Brasil: Code Smells, Refactoring and Elixir (in Portuguese) https://www.youtube.com/watch?v=3-1PCurON4Q

    https://speakerdeck.com/elainenaomi/elixir-brasil-2020-code-smells-refactoring-e-elixir One of my talks was part of the data sources analyzed in the grey literature review
  8. defmodule ShoppingCart do def calculate_shipping(zip_code, subscription) do if (Enum.member?([3, 4],

    subscription.id)) do 0 else 10.0 * Location.calculate(zip_code) end end def apply_discount(total, subscription) do if (Enum.member?([3, 4], subscription.id)) do total * 0.9 else total end end end
  9. defmodule ShoppingCart do def calculate_shipping(zip_code, subscription) do if (Enum.member?([3, 4],

    subscription.id)) do 0 else 10.0 * Location.calculate(zip_code) end end def apply_discount(total, subscription) do if (Enum.member?([3, 4], subscription.id)) do total * 0.9 else total end end end Duplicated business rule
  10. 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
  11. 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) Duplicated business rule
  12. defmodule Subscription do defstruct [:id, :shipping_tax, :discount_rate] def available_subscriptions do

    [ %Subscription{id: 3, shipping_tax: 0.0, discount_rate: 0.95}, %Subscription{id: 4, shipping_tax: 0.0, discount_rate: 0.9} ] end end Encapsulating the rules, centralizing them in one single place
  13. 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
  14. 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 Now, the subscription type is transparent for this module
  15. defmodule User do def create_user(%User{} = user) do # ...

    end end new_user = %User{ name: "Elaine", email: "[email protected]", password: "123", username: "elainenaomi", status: 1, active?: true, admin?: false } User.create_user(new_user)
  16. defmodule Report do def print_status(value) do if is_integer(value) do #...

    "Result..." else raise RuntimeError, message: "Invalid argument. It is not integer!" end end end
  17. defmodule Report do def print_status(value) do if is_integer(value) do #...

    "Result..." else raise RuntimeError, message: "Invalid argument. It is not integer!" end end end
  18. defmodule Client do # Client forced to use exceptions for

    control-flow. def status(arg) do try do value = Report.print_status(arg) "All good! #{value}." rescue e in RuntimeError -> reason = e.message "Uh oh! #{reason}." end end end
  19. defmodule Client do # Client forced to use exceptions for

    control-flow. def status(arg) do try do value = Report.print_status(arg) "All good! #{value}." rescue e in RuntimeError -> reason = e.message "Uh oh! #{reason}." end end end
  20. defmodule Report do def print_status(value) do if is_integer(value) do #...

    "Result..." else raise RuntimeError, message: "Invalid argument. It is not integer!" end end end
  21. defmodule Report do def print_status(value) do if is_integer(value) do #...

    {:ok, "Result..."} else {:error, "Invalid argument. It is not integer!" end end end Error Monad
  22. defmodule Client do # Client forced to use exceptions for

    control-flow. def status(arg) do try do value = Report.print_status(arg) "All good! #{value}." rescue e in RuntimeError -> reason = e.message "Uh oh! #{reason}." end end end
  23. defmodule Client do # Client forced to use exceptions for

    control-flow. def status(arg) do case Report.print_status(arg) do {:ok, value} -> "All good! #{value}." {:error, reason} -> "Uh oh! #{reason}." end end end
  24. def drive(%User{name: name, age: age}) when age >= 18 do

    "#{name} can drive" end def drive(%User{name: name, age: age}) when age < 18 do "#{name} cannot drive" end Extracting values for further usage and pattern/guard checking
  25. def drive(%User{age: age} = user) when age >= 18 do

    %User{name: name} = user "#{name} can drive" end def drive(%User{age: age} = user) when age < 18 do %User{name: name} = user "#{name} cannot drive" end Extracting only pattern/guard-related variables
  26. def drive(%User{age: age} = user) when age >= 18 do

    "#{user.name} can drive" end def drive(%User{age: age} = user) when age < 18 do "#{user.name} cannot drive" end Using dot notation for accessing struct properties
  27. Elixir 1.16 will contain the complete catalog of code smells

    in Elixir They will be called "anti-patterns"
  28. 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
  29. 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
  30. Developer’s experience and system’s knowledge play an important role in

    the identification of some smells. F. Palomba, G. Bavota, M. Di Penta, R. Oliveto and A. De Lucia, "Do They Really Smell Bad? A Study on Developers' Perception of Bad Code Smells," 2014 IEEE International Conference on Software Maintenance and Evolution, Victoria, BC, Canada, 2014, pp. 101-110, doi: 10.1109/ICSME.2014.32.
  31. Research indicates that almost 60% of programmers’ time is spent

    understanding code, rather than writing code. From The Programmer's Brain - Felienne Hermans
  32. Developer productivity Product evolvability Quality of end-product Self-improvement etc "Do

    developers care about code smells? An exploratory survey" A. Yamashita and L. Moonen, 2013 doi: 10.1109/WCRE.2013.6671299.
  33. #

  34. speakerdeck.com/elainenaomi elainenaomi.dev Muito obrigada illustrations from undraw.co translation: thank you

    so much! Don't forget to check and participate in the discussions about the catalog of refactorings