Matter AI | Code Reviewer Documentation home pagelight logodark logo
  • Contact
  • Github
  • Sign in
  • Sign in
  • Documentation
  • Blog
  • Discord
  • Github
  • Introduction
    • What is Matter AI?
    Getting Started
    • QuickStart
    Product
    • Security Analysis
    • Code Quality
    • Agentic Chat
    • RuleSets
    • Memories
    • Analytics
    • Command List
    • Configurations
    Patterns
    • Languages
      • Supported Languages
      • Python
      • Java
      • JavaScript
      • TypeScript
      • Node.js
      • React
      • Fastify
      • Next.js
      • Terraform
      • C#
      • C++
      • C
      • Go
      • Rust
      • Swift
      • React Native
      • Spring Boot
      • Kotlin
      • Flutter
      • Ruby
      • PHP
      • Scala
      • Perl
      • R
      • Dart
      • Elixir
      • Erlang
      • Haskell
      • Lua
      • Julia
      • Clojure
      • Groovy
      • Fortran
      • COBOL
      • Pascal
      • Assembly
      • Bash
      • PowerShell
      • SQL
      • PL/SQL
      • T-SQL
      • MATLAB
      • Objective-C
      • VBA
      • ABAP
      • Apex
      • Apache Camel
      • Crystal
      • D
      • Delphi
      • Elm
      • F#
      • Hack
      • Lisp
      • OCaml
      • Prolog
      • Racket
      • Scheme
      • Solidity
      • Verilog
      • VHDL
      • Zig
      • MongoDB
      • ClickHouse
      • MySQL
      • GraphQL
      • Redis
      • Cassandra
      • Elasticsearch
    • Security
    • Performance
    Integrations
    • Code Repositories
    • Team Messengers
    • Ticketing
    Enterprise
    • Enterprise Deployment Overview
    • Enterprise Configurations
    • Observability and Fallbacks
    • Create Your Own GitHub App
    • Self-Hosting Options
    • RBAC
    Patterns
    Languages

    Elixir

    Elixir is a dynamic, functional programming language designed for building scalable and maintainable applications. It leverages the Erlang VM, known for running low-latency, distributed, and fault-tolerant systems.

    Elixir, despite being a well-designed functional language, has several common anti-patterns that can lead to performance issues, maintainability problems, and bugs. Here are the most important anti-patterns to avoid when writing Elixir code.

    # Anti-pattern: Using processes unnecessarily
    defmodule Calculator do
      def start do
        spawn(fn -> loop(%{}) end)
      end
      
      def loop(state) do
        receive do
          {:add, a, b, caller} ->
            send(caller, {:result, a + b})
            loop(state)
          # Other operations...
        end
      end
    end
    
    # Usage
    calculator = Calculator.start()
    send(calculator, {:add, 2, 3, self()})
    
    receive do
      {:result, value} -> IO.puts("Result: #{value}")
    end
    
    # Better approach: Use regular functions for simple operations
    defmodule Calculator do
      def add(a, b), do: a + b
      # Other operations...
    end
    
    # Usage
    result = Calculator.add(2, 3)
    IO.puts("Result: #{result}")

    Don’t use processes for simple operations that don’t need concurrency, state isolation, or fault tolerance. Processes have overhead and should be used when their benefits are needed.

    # Anti-pattern: Reinventing OTP patterns
    defmodule UserStore do
      def start do
        spawn(fn -> loop(%{}) end)
      end
      
      def loop(state) do
        receive do
          {:get, key, caller} ->
            send(caller, {:ok, Map.get(state, key)})
            loop(state)
          {:put, key, value} ->
            loop(Map.put(state, key, value))
          # No proper error handling, supervision, etc.
        end
      end
    end
    
    # Better approach: Use GenServer
    defmodule UserStore do
      use GenServer
      
      # Client API
      def start_link(opts \\ []) do
        GenServer.start_link(__MODULE__, %{}, opts)
      end
      
      def get(pid, key) do
        GenServer.call(pid, {:get, key})
      end
      
      def put(pid, key, value) do
        GenServer.cast(pid, {:put, key, value})
      end
      
      # Server callbacks
      @impl true
      def init(state) do
        {:ok, state}
      end
      
      @impl true
      def handle_call({:get, key}, _from, state) do
        {:reply, Map.get(state, key), state}
      end
      
      @impl true
      def handle_cast({:put, key, value}, state) do
        {:noreply, Map.put(state, key, value)}
      end
    end

    Use OTP behaviors like GenServer, Supervisor, and Application instead of reinventing them. They provide battle-tested solutions for common patterns.

    # Anti-pattern: Using Agent for complex state management
    defmodule ComplexStore do
      def start_link do
        Agent.start_link(fn -> %{} end)
      end
      
      def get_user_posts(agent, user_id) do
        Agent.get(agent, fn state ->
          # Complex data processing inside Agent
          posts = Map.get(state, :posts, %{})
          user_posts = Map.get(posts, user_id, [])
          Enum.filter(user_posts, &(&1.published))
          |> Enum.sort_by(&(&1.date), {:desc, Date})
        end)
      end
      
      def add_post(agent, user_id, post) do
        Agent.update(agent, fn state ->
          posts = Map.get(state, :posts, %{})
          user_posts = Map.get(posts, user_id, [])
          updated_posts = [post | user_posts]
          Map.put(state, :posts, Map.put(posts, user_id, updated_posts))
        end)
      end
    end
    
    # Better approach: Use GenServer for complex state
    defmodule ComplexStore do
      use GenServer
      
      # Client API
      def start_link(opts \\ []) do
        GenServer.start_link(__MODULE__, %{}, opts)
      end
      
      def get_user_posts(pid, user_id) do
        GenServer.call(pid, {:get_user_posts, user_id})
      end
      
      def add_post(pid, user_id, post) do
        GenServer.cast(pid, {:add_post, user_id, post})
      end
      
      # Server callbacks
      @impl true
      def init(state) do
        {:ok, state}
      end
      
      @impl true
      def handle_call({:get_user_posts, user_id}, _from, state) do
        posts = Map.get(state, :posts, %{})
        user_posts = Map.get(posts, user_id, [])
        result = Enum.filter(user_posts, &(&1.published))
                |> Enum.sort_by(&(&1.date), {:desc, Date})
        {:reply, result, state}
      end
      
      @impl true
      def handle_cast({:add_post, user_id, post}, state) do
        posts = Map.get(state, :posts, %{})
        user_posts = Map.get(posts, user_id, [])
        updated_posts = [post | user_posts]
        new_state = Map.put(state, :posts, Map.put(posts, user_id, updated_posts))
        {:noreply, new_state}
      end
    end

    Use Agent only for simple state. For complex state management with custom logic, use GenServer which gives you more control and better performance.

    # Anti-pattern: Not using pattern matching
    def process_response(response) do
      if response.status == :ok do
        data = response.body
        # Process data
      else
        error = response.error
        # Handle error
      end
    end
    
    # Better approach: Use pattern matching
    def process_response({:ok, body}) do
      # Process data
    end
    
    def process_response({:error, reason}) do
      # Handle error
    end

    Leverage Elixir’s pattern matching for cleaner, more declarative code. Use it in function heads, case statements, and with expressions.

    # Anti-pattern: Using strings for map keys
    user = %{"name" => "John", "age" => 30}
    name = user["name"]
    
    # Better approach: Use atoms for map keys
    user = %{name: "John", age: 30}
    name = user.name  # More concise access

    Use atoms for map keys when the set of keys is known and limited. Atoms are more efficient and allow for the dot notation. However, be careful not to create atoms dynamically from user input as they are not garbage collected.

    # Anti-pattern: Nested case statements
    def process_data(data) do
      case parse_data(data) do
        {:ok, parsed} ->
          case validate_data(parsed) do
            {:ok, validated} ->
              case save_data(validated) do
                {:ok, result} -> {:ok, result}
                {:error, reason} -> {:error, reason}
              end
            {:error, reason} -> {:error, reason}
          end
        {:error, reason} -> {:error, reason}
      end
    end
    
    # Better approach: Use with
    def process_data(data) do
      with {:ok, parsed} <- parse_data(data),
           {:ok, validated} <- validate_data(parsed),
           {:ok, result} <- save_data(validated) do
        {:ok, result}
      end
    end

    Use with expressions for sequences of operations where each step depends on the success of the previous one. It makes the code more readable and avoids the “pyramid of doom”.

    # Anti-pattern: Type checking
    def stringify(value) do
      cond do
        is_integer(value) -> Integer.to_string(value)
        is_float(value) -> Float.to_string(value)
        is_list(value) -> Enum.join(value, ", ")
        true -> raise "Unsupported type"
      end
    end
    
    # Better approach: Use protocols
    defprotocol Stringify do
      def to_string(value)
    end
    
    defimpl Stringify, for: Integer do
      def to_string(value), do: Integer.to_string(value)
    end
    
    defimpl Stringify, for: Float do
      def to_string(value), do: Float.to_string(value)
    end
    
    defimpl Stringify, for: List do
      def to_string(value), do: Enum.join(value, ", ")
    end

    Use protocols for polymorphic behavior instead of type checking. They provide a clean way to implement behavior that varies based on type.

    # Anti-pattern: Inefficient list concatenation
    def process_items(items) do
      Enum.reduce(items, [], fn item, acc ->
        processed = process_item(item)
        acc ++ [processed]  # Inefficient: O(n) operation
      end)
    end
    
    # Better approach: Use list prepending and reverse
    def process_items(items) do
      items
      |> Enum.reduce([], fn item, acc ->
        processed = process_item(item)
        [processed | acc]  # Efficient: O(1) operation
      end)
      |> Enum.reverse()  # Reverse once at the end
    end

    Avoid using ++ for list concatenation in loops. Instead, use list prepending ([head | tail]) and reverse the result at the end if order matters.

    # Anti-pattern: Eager evaluation of large collections
    def process_large_file(file_path) do
      File.stream!(file_path)
      |> Enum.map(&String.trim/1)
      |> Enum.filter(&(String.length(&1) > 0))
      |> Enum.map(&process_line/1)
      |> Enum.take(10)
    end
    
    # Better approach: Use Stream for lazy evaluation
    def process_large_file(file_path) do
      File.stream!(file_path)
      |> Stream.map(&String.trim/1)
      |> Stream.filter(&(String.length(&1) > 0))
      |> Stream.map(&process_line/1)
      |> Enum.take(10)  # Only now is the stream evaluated
    end

    Use Stream instead of Enum for large collections or when you only need part of the result. Streams are lazy and avoid unnecessary computation.

    # Anti-pattern: Manual process management
    defmodule MyApp do
      def start do
        database_pid = Database.start_link()
        cache_pid = Cache.start_link()
        api_pid = API.start_link(database_pid, cache_pid)
        # What if one of these crashes?
      end
    end
    
    # Better approach: Use supervision trees
    defmodule MyApp.Application do
      use Application
      
      def start(_type, _args) do
        children = [
          MyApp.Database,
          MyApp.Cache,
          {MyApp.API, []}
        ]
        
        opts = [strategy: :one_for_one, name: MyApp.Supervisor]
        Supervisor.start_link(children, opts)
      end
    end

    Use proper supervision trees to manage process lifecycles and handle failures. This is a core strength of the BEAM VM and Elixir’s OTP framework.

    # Anti-pattern: Not awaiting tasks
    def process_items(items) do
      items
      |> Enum.map(fn item ->
        Task.async(fn -> process_item(item) end)
      end)
      # Tasks are started but never awaited
    end
    
    # Better approach: Properly await tasks
    def process_items(items) do
      tasks = Enum.map(items, fn item ->
        Task.async(fn -> process_item(item) end)
      end)
      
      Enum.map(tasks, &Task.await/1)
    end

    Always call Task.await/1 or Task.await/2 on tasks started with Task.async/1 to avoid memory leaks and ensure proper error propagation.

    # Anti-pattern: Manual testing
    defmodule Calculator do
      def add(a, b), do: a + b
    end
    
    # Manual test
    IO.puts("Testing add...")
    result = Calculator.add(2, 3)
    if result == 5 do
      IO.puts("Test passed!")
    else
      IO.puts("Test failed!")
    end
    
    # Better approach: Use ExUnit
    defmodule CalculatorTest do
      use ExUnit.Case
      
      test "adds two numbers" do
        assert Calculator.add(2, 3) == 5
      end
      
      test "handles negative numbers" do
        assert Calculator.add(-2, 3) == 1
        assert Calculator.add(2, -3) == -1
      end
    end

    Use ExUnit for testing instead of writing manual test code. It provides a structured way to write and run tests with helpful assertions and reporting.

    # Anti-pattern: No typespecs
    defmodule User do
      def create(name, age) do
        %{name: name, age: age}
      end
      
      def adult?(user) do
        user.age >= 18
      end
    end
    
    # Better approach: Use typespecs
    defmodule User do
      @type t :: %{name: String.t(), age: non_neg_integer()}
      
      @spec create(String.t(), non_neg_integer()) :: t()
      def create(name, age) do
        %{name: name, age: age}
      end
      
      @spec adult?(t()) :: boolean()
      def adult?(user) do
        user.age >= 18
      end
    end

    Use typespecs to document function signatures and data structures. They improve code documentation and allow tools like Dialyzer to perform static analysis.

    # Anti-pattern: Excessive use of try/rescue
    def divide(a, b) do
      try do
        a / b
      rescue
        ArithmeticError -> 0
      end
    end
    
    # Better approach: Use pattern matching and guard clauses
    def divide(a, _b = 0), do: {:error, :division_by_zero}
    def divide(a, b), do: {:ok, a / b}

    Avoid using try/rescue for control flow. In Elixir, it’s more idiomatic to use pattern matching, guard clauses, and tagged tuples for error handling.

    # Anti-pattern: Poor module structure
    defmodule UserSystem do
      # Hundreds of functions in one module
      def create_user(params), do: # ...
      def update_user(id, params), do: # ...
      def delete_user(id), do: # ...
      def get_user(id), do: # ...
      def list_users, do: # ...
      def authenticate_user(email, password), do: # ...
      def generate_password_reset_token(email), do: # ...
      # And many more...
    end
    
    # Better approach: Proper module organization
    defmodule MyApp.Accounts do
      # Public API for account management
      def create_user(params), do: # ...
      def update_user(id, params), do: # ...
      def delete_user(id), do: # ...
      def get_user(id), do: # ...
      def list_users, do: # ...
    end
    
    defmodule MyApp.Accounts.Authentication do
      # Authentication-specific functionality
      def authenticate_user(email, password), do: # ...
      def generate_password_reset_token(email), do: # ...
    end
    
    defmodule MyApp.Accounts.User do
      # User struct and validations
      defstruct [:id, :name, :email, :password_hash]
      
      def changeset(user, params), do: # ...
    end

    Organize your code into cohesive modules with clear responsibilities. Follow the principle of single responsibility and create a logical hierarchy of modules.

    # Anti-pattern: Inconsistent error handling
    def create_user(params) do
      if valid_params?(params) do
        # Create user
        %{id: 123, name: params.name}
      else
        nil  # What went wrong?
      end
    end
    
    # Usage
    user = create_user(params)
    if user do
      # Success
    else
      # Error, but what kind?
    end
    
    # Better approach: Use tagged tuples
    def create_user(params) do
      case validate_params(params) do
        :ok ->
          # Create user
          {:ok, %{id: 123, name: params.name}}
        {:error, reason} ->
          {:error, reason}
      end
    end
    
    # Usage
    case create_user(params) do
      {:ok, user} ->
        # Success
      {:error, :invalid_email} ->
        # Handle specific error
      {:error, reason} ->
        # Handle other errors
    end

    Use tagged tuples like {:ok, result} and {:error, reason} for functions that can fail. This makes error handling explicit and allows for pattern matching on specific error types.

    # Anti-pattern: Poor or no documentation
    defmodule UserAPI do
      def create(params) do
        # Implementation...
      end
    end
    
    # Better approach: Proper documentation
    defmodule UserAPI do
      @moduledoc """
      Provides functions for managing users in the system.
      """
      
      @doc """
      Creates a new user with the given parameters.
      
      ## Parameters
      
        * `params` - A map containing user attributes:
          * `:name` - The user's full name (required)
          * `:email` - The user's email address (required)
          * `:age` - The user's age (optional)
      
      ## Examples
      
          iex> UserAPI.create(%{name: "John Doe", email: "john@example.com"})
          {:ok, %User{id: 123, name: "John Doe", email: "john@example.com"}}
          
          iex> UserAPI.create(%{name: "John Doe"})
          {:error, :email_required}
      
      ## Returns
      
        * `{:ok, user}` if the user was created successfully
        * `{:error, reason}` if there was an error
      """
      def create(params) do
        # Implementation...
      end
    end

    Write comprehensive documentation for your modules and functions using @moduledoc and @doc attributes. Include examples, parameter descriptions, and return value information.

    # Anti-pattern: String interpolation for SQL queries
    def find_users(name, min_age) do
      query = "SELECT * FROM users WHERE name LIKE '%#{name}%' AND age >= #{min_age}"
      # Execute query...
    end
    
    # Better approach: Use parameterized queries
    def find_users(name, min_age) do
      query = "SELECT * FROM users WHERE name LIKE $1 AND age >= $2"
      params = ["%#{name}%", min_age]
      # Execute query with params...
    end
    
    # Even better with Ecto
    def find_users(name, min_age) do
      from(u in User,
        where: like(u.name, ^"%#{name}%") and u.age >= ^min_age
      )
      |> Repo.all()
    end

    Never use string interpolation for building database queries. Use parameterized queries or query builders like Ecto to prevent SQL injection attacks.

    # Anti-pattern: Hardcoded configuration
    defmodule MyApp.API do
      def base_url do
        "https://api.example.com"
      end
      
      def api_key do
        "hardcoded_api_key"
      end
    end
    
    # Better approach: Use application configuration
    # In config/config.exs
    config :my_app, :api,
      base_url: "https://api.example.com",
      api_key: System.get_env("API_KEY")
    
    # In your module
    defmodule MyApp.API do
      def base_url do
        Application.get_env(:my_app, :api)[:base_url]
      end
      
      def api_key do
        Application.get_env(:my_app, :api)[:api_key]
      end
    end

    Use Elixir’s application configuration system instead of hardcoding configuration values. This allows for different configurations in different environments.

    DartErlang
    websitexgithublinkedin
    Powered by Mintlify
    Assistant
    Responses are generated using AI and may contain mistakes.