Elixir Style Guide

repository·master·Indexed 18 days ago

https://github.com/rrrene/elixir-style-guide

A practical guide to Elixir coding standards focusing on consistency, readability, and maintainability. It serves as the philosophical basis for the Credo static analysis tool, providing guidelines on formatting, naming conventions, documentation best practices, and refactoring to reduce complexity.

Tokens
2K
Snippets
0
Records
12
Agent score
14%

What's inside elixir-style-guide

  1. Understand the purpose and philosophy of the Elixir Style Guide

    master

    This style guide serves two primary purposes: it represents a personal standard for writing readable Elixir and acts as the foundational principles for Credo, an Elixir code analyzer.

    The guide is non-dogmatic and focuses on three core principles:

    1. Consistency: Apply the same stylistic choices throughout your entire codebase.
    2. Readability: Prioritize code that is easy to comprehend (e.g., prefer spreading text vertically over horizontal density when in doubt).
    3. Maintainability: Use clear, non-confusing names and structures to ease long-term maintenance.

    Following these principles helps make open-source Elixir code more accessible to the community.

  2. Refactoring: Reducing Complexity

    master

    To keep code maintainable, follow these refactoring rules:

    • Avoid Nested Conditionals: Never nest if, unless, or case more than once. If logic requires deeper nesting, split it into multiple functions.
    • Avoid unless with else: Never use unless with an else block. Rewrite it using if with the positive case first.
    • Avoid Double Negations: Never use unless with a negated expression (e.g., unless !condition). Rewrite it using if.
    • Use __MODULE__: Always use the __MODULE__ macro when referencing the current module.
    # preferred: splitting nested logic into functions
    defp perform_task(true, hash, config) do
      hash |> Map.get(:action) |> perform_action(config)
    end
    
    # preferred: replacing unless/else with if
    if allowed? do
      proceed_as_planned
    else
      raise "Not allowed!"
    end
  3. Code Readability: Line Length and Semicolons

    master

    To maintain readability:

    • Line Length: Keep lines under 80 characters whenever possible.
    • Semicolons: Do not use ; to separate statements and expressions. Use newlines instead.
    # preferred way
    IO.puts "Waiting for:"
    IO.inspect object
    
    # NOT okay
    IO.puts "Waiting for:"; IO.inspect object
  4. Naming Conventions

    master

    Follow these naming patterns for Elixir modules and identifiers:

    • Modules: Use CamelCase. Acronyms like HTTP or XML should remain uppercase.
    • Attributes, Functions, Macros, and Variables: Use snake_case.
    • Exceptions: Use a common prefix or suffix (e.g., ending in Error).
    • Predicates (Functions): Should return a boolean and end in a question mark (?).
    • Predicates (Guard-safe Macros): Should have the is_ prefix and not end in a question mark.
    # Module naming
    defmodule MyApp.HTTPService do; end
    
    # Attribute/Function naming
    @some_setting :my_value
    def my_function(param_value) do; end
    
    # Exception naming
    defmodule BadHTTPHeaderError do; end
    
    # Predicate naming
    def valid?(username) do; end
    defmacro is_valid(username) do; end
  5. Software Design and Debugging Pitfalls

    master

    Design Patterns

    • Task Marking: Use FIXME: for bugs/issues and TODO: for planned changes. This allows tools to report them.
    • Module Aliasing: Alias all used modules within a module to make dependencies visible at a glance. This helps maintain architectural boundaries.
    defmodule Test do
      alias MyApp.External.TwitterAPI
    
      def something do
        TwitterAPI.search(...) 
      end
    end

    Pitfalls to Avoid

    • Production Debugging: Never leave IEx.pry in production code. Be wary of IO.inspect in production; use Logger combined with &inspect/1 instead.
    • Conditionals: Avoid expressions in conditionals that always evaluate to the same value (e.g., true, false, x == x).
    • Naming Collisions:
      • Avoid naming variables/functions the same as Kernel functions (especially arity 0).
      • Avoid naming modules the same as stdlib modules to prevent aliasing issues (e.g., use YourProject.DataTypeString instead of YourProject.DataType.String).
  6. Documentation Best Practices

    master

    Guidelines for documenting Elixir code:

    • Coverage: Every module and function should either be documented or explicitly marked with @moduledoc false or @doc false.
    • Tooling: Use ExDoc for generating documentation.
    • Style:
      • Put an empty line after @moduledoc.
      • Do not put an empty line between @doc and the function/macro definition.
    • Comments: Use standard code comments for additional information, but do not use them to explain bad or confusing code.
    • Long Comments: Place long, descriptive comments on their own line rather than at the end of a line of code.
    defmodule MyApp.HTTPService do
      @moduledoc false
    
      @doc "Sends a POST request to the given `url`."
      def post(url) do
        # ...
      end
    end
  7. Code Readability: Formatting and Whitespace

    master

    Follow these rules for consistent Elixir code formatting:

    • Indentation: Use tabs consistently. 2-space soft-tabs are preferred.
    • Line Endings: Use Unix-style line endings.
    • Trailing Whitespace: Do not leave trailing whitespace at the end of lines.
    • End of File: End every file with a newline.
    • Operators and Commas: Use spaces around operators and after commas.
    • Braces: Do not use spaces after (, [, { or before }, ], ).
    # preferred way
    Helper.format({1, true, 2}, :my_atom)
    
    # also okay - choose a style and use it consistently
    Helper.format( { 1, true, 2 }, :my_atom )
  8. Code Readability: Function and Macro Parentheses

    master

    Function Definitions

    • Use parentheses for def, defp, and defmacro when they take parameters.
    • Omit parentheses when the function accepts no parameters.
    # preferred way
    def time do
      # ...
    end
    
    def convert(x, y) do
      # ...
    end

    Function Calling

    • When calling functions with parameters, it is preferred to use parentheses.
    # preferred way
    Enum.reduce(1..100, 0, &(&1 + &2))
    
    # also okay
    Enum.reduce 1..100, 0, & &1 + &2

    Macros and Conditionals

    • Macros: The preferred way is to not use parentheses for macros (e.g., use).
    • Conditionals: Never use parentheses around the condition of if or unless.
    # preferred way (macros)
    use MyApp.Service, social: true
    
    # preferred way (conditionals)
    if valid?(username) do
      # ...
    end
  9. Sigils and Regular Expressions

    master

    Sigils

    Use sigils when they improve readability (e.g., avoiding excessive escaping), but do not use them dogmatically. If you use a custom sigil, stick to one delimiter (e.g., don't mix ~S{} and ~S()).

    Regular Expressions

    • Use ~r// as the default sigil for regexes.
    • Use other ~r delimiters (like ~r{}) if the expression contains many slashes.
    • Warning: ^ and $ match start/end of line. To match the whole string, use \A and \z.
    # preferred regex
    regex = ~r/\d+/
    regex = ~r{http://example.com/(.+).html}
  10. Code Readability: Negation and Function Grouping

    master

    Negation

    Do not put a space after ! when negating an expression.

    # preferred way
    denied = !allowed?
    
    # NOT okay
    denied = ! allowed?

    Function Grouping

    • Group function definitions with different signatures of the same function together without blank lines.
    • Use blank lines to separate different functions or different parts of a module to provide vertical white-space.
    defp find_properties(source_file, config) do
      {property_for(source_file, config), source_file}
    end
    
    defp property_for(source_file, _config) do
      Enum.map(lines, &tabs_or_spaces/1)
    end
    
    defp tabs_or_spaces({_, "\t" <> line}), do: :tabs
    defp tabs_or_spaces({_, "  " <> line}), do: :spaces
    defp tabs_or_spaces({_, line}), do: nil
  11. Code Readability: Multi-line Calls and Numerics

    master

    Multi-line Calls

    When assigning a multi-line call to a variable, begin a new line after the = and indent the calculation by one level.

    # preferred way
    result =
      lines
      |> Enum.map(&tabs_or_spaces/1)
      |> Enum.uniq
    
    # also okay
    result = lines
             |> Enum.map(&tabs_or_spaces/1)
             |> Enum.uniq

    Numerics

    Use underscores in large numbers to improve readability.

    # preferred way
    num = 10_000_000
    
    # NOT okay
    num = 10000000
  12. Code Readability: Vertical Space and Pipe Chains

    master

    Vertical Space

    Use vertical white-space to improve the readability of code sections. Combining descriptive variable names and vertical space is preferred over dense, complex logic.

    Pipe Chains

    It is preferred to start pipe chains with a "pure" value rather than a function call to ensure a clear flow of data.

    # preferred way
    username
    |> String.strip
    |> String.downcase
    
    # also okay
    String.strip(username)
    |> String.downcase