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

    Erlang

    Erlang is a general-purpose, concurrent, functional programming language designed for building massively scalable, soft real-time systems with high availability requirements.

    Erlang, despite being a powerful language for building concurrent and distributed systems, 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 Erlang code.

    %% Anti-pattern: Using processes unnecessarily
    start() ->
        spawn(fun() -> calculator_loop() end).
    
    calculator_loop() ->
        receive
            {add, A, B, Caller} ->
                Caller ! {result, A + B},
                calculator_loop();
            {subtract, A, B, Caller} ->
                Caller ! {result, A - B},
                calculator_loop()
        end.
    
    %% Usage
    use_calculator() ->
        Calc = start(),
        Calc ! {add, 2, 3, self()},
        receive
            {result, Result} -> Result
        end.
    
    %% Better approach: Use regular functions for simple operations
    add(A, B) -> A + B.
    subtract(A, B) -> A - B.
    
    %% Usage
    use_calculator() ->
        add(2, 3).

    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
    -module(user_store).
    -export([start/0, store/3, retrieve/2]).
    
    start() ->
        spawn(fun() -> loop(#{}) end).
    
    store(Pid, Key, Value) ->
        Pid ! {store, Key, Value}.
    
    retrieve(Pid, Key) ->
        Pid ! {retrieve, Key, self()},
        receive
            {value, Value} -> Value
        after 1000 ->
            {error, timeout}
        end.
    
    loop(State) ->
        receive
            {store, Key, Value} ->
                loop(maps:put(Key, Value, State));
            {retrieve, Key, Caller} ->
                Caller ! {value, maps:get(Key, State, undefined)},
                loop(State)
        end.
    
    %% Better approach: Use gen_server
    -module(user_store).
    -behaviour(gen_server).
    
    %% API
    -export([start_link/0, store/2, retrieve/1]).
    
    %% gen_server callbacks
    -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
    
    start_link() ->
        gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
    
    store(Key, Value) ->
        gen_server:cast(?MODULE, {store, Key, Value}).
    
    retrieve(Key) ->
        gen_server:call(?MODULE, {retrieve, Key}).
    
    %% Callbacks
    init([]) ->
        {ok, #{}}.
    
    handle_call({retrieve, Key}, _From, State) ->
        {reply, maps:get(Key, State, undefined), State}.
    
    handle_cast({store, Key, Value}, State) ->
        {noreply, maps:put(Key, Value, State)}.
    
    handle_info(_Info, State) ->
        {noreply, State}.
    
    terminate(_Reason, _State) ->
        ok.
    
    code_change(_OldVsn, State, _Extra) ->
        {ok, State}.

    Use OTP behaviors like gen_server, supervisor, and application instead of reinventing them. They provide battle-tested solutions for common patterns.

    %% Anti-pattern: Inefficient list concatenation
    process_items(Items) ->
        process_items(Items, []).
    
    process_items([], Acc) ->
        Acc;
    process_items([Item | Rest], Acc) ->
        Processed = process_item(Item),
        process_items(Rest, Acc ++ [Processed]). %% Inefficient: O(n) operation
    
    %% Better approach: Use list prepending and reverse
    process_items(Items) ->
        process_items(Items, []).
    
    process_items([], Acc) ->
        lists:reverse(Acc); %% Reverse once at the end
    process_items([Item | Rest], Acc) ->
        Processed = process_item(Item),
        process_items(Rest, [Processed | Acc]). %% Efficient: O(1) operation

    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: Not using pattern matching
    process_response(Response) ->
        case Response of
            {ok, Body} ->
                %% Process data
                {ok, process_body(Body)};
            {error, Reason} ->
                %% Handle error
                {error, Reason}
        end.
    
    %% Better approach: Use pattern matching in function heads
    process_response({ok, Body}) ->
        %% Process data
        {ok, process_body(Body)};
    process_response({error, Reason}) ->
        %% Handle error
        {error, Reason}.

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

    %% Anti-pattern: Inefficient string operations
    join_strings(Strings) ->
        join_strings(Strings, "").
    
    join_strings([], Acc) ->
        Acc;
    join_strings([String | Rest], Acc) ->
        join_strings(Rest, Acc ++ String). %% Inefficient for strings
    
    %% Better approach: Use binaries for strings
    join_binaries(Binaries) ->
        join_binaries(Binaries, <<>>).
    
    join_binaries([], Acc) ->
        Acc;
    join_binaries([Binary | Rest], Acc) ->
        join_binaries(Rest, <<Acc/binary, Binary/binary>>).

    Use binaries instead of lists for string operations. Binaries are more efficient for string manipulation in Erlang.

    %% Anti-pattern: Using tuples for structured data
    create_user(Name, Email, Age) ->
        {user, Name, Email, Age}.
    
    get_user_name({user, Name, _Email, _Age}) ->
        Name.
    
    %% Better approach: Use records
    -record(user, {name, email, age}).
    
    create_user(Name, Email, Age) ->
        #user{name = Name, email = Email, age = Age}.
    
    get_user_name(#user{name = Name}) ->
        Name.
    
    %% Or even better in modern Erlang: Use maps
    create_user(Name, Email, Age) ->
        #{type => user, name => Name, email => Email, age => Age}.
    
    get_user_name(#{type := user, name := Name}) ->
        Name.

    Use records or maps for structured data instead of raw tuples. They provide named fields and better pattern matching.

    %% Anti-pattern: Ignoring errors
    read_file(Filename) ->
        case file:read_file(Filename) of
            {ok, Content} ->
                Content;
            {error, _Reason} ->
                <<>> %% Silent failure with empty binary
        end.
    
    %% Better approach: Proper error handling
    read_file(Filename) ->
        case file:read_file(Filename) of
            {ok, Content} ->
                {ok, Content};
            {error, Reason} ->
                {error, {file_read_error, Filename, Reason}}
        end.

    Use proper error handling with tagged tuples ({ok, Result} and {error, Reason}) and propagate errors up the call stack.

    %% Anti-pattern: Excessive use of try/catch
    divide(A, B) ->
        try
            A / B
        catch
            error:badarith ->
                0
        end.
    
    %% Better approach: Use pattern matching and guards
    divide(A, B) when B /= 0 ->
        {ok, A / B};
    divide(_A, 0) ->
        {error, division_by_zero}.

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

    %% Anti-pattern: Manual process management
    start_system() ->
        DatabasePid = database:start_link(),
        CachePid = cache:start_link(),
        ApiPid = api:start_link(DatabasePid, CachePid),
        {ok, ApiPid}.
    %% What if one of these crashes?
    
    %% Better approach: Use supervision trees
    -module(my_app_sup).
    -behaviour(supervisor).
    
    -export([start_link/0, init/1]).
    
    start_link() ->
        supervisor:start_link({local, ?MODULE}, ?MODULE, []).
    
    init([]) ->
        SupFlags = #{strategy => one_for_one, intensity => 10, period => 60},
        ChildSpecs = [
            #{id => database, start => {database, start_link, []}, restart => permanent, shutdown => 5000, type => worker},
            #{id => cache, start => {cache, start_link, []}, restart => permanent, shutdown => 5000, type => worker},
            #{id => api, start => {api, start_link, []}, restart => permanent, shutdown => 5000, type => worker}
        ],
        {ok, {SupFlags, ChildSpecs}}.

    Use proper supervision trees to manage process lifecycles and handle failures. This is a core strength of the Erlang VM.

    %% Anti-pattern: receive without timeout
    get_data(Pid) ->
        Pid ! {get_data, self()},
        receive
            {data, Data} -> Data
        end. %% Could block forever
    
    %% Better approach: Always use timeouts with receive
    get_data(Pid) ->
        Pid ! {get_data, self()},
        receive
            {data, Data} -> {ok, Data}
        after 5000 ->
            {error, timeout}
        end.

    Always use timeouts with receive to prevent processes from blocking indefinitely if the expected message never arrives.

    %% Anti-pattern: Using processes for simple shared state
    -module(counter).
    -export([start/0, increment/1, get/1]).
    
    start() ->
        spawn(fun() -> loop(0) end).
    
    increment(Pid) ->
        Pid ! increment,
        ok.
    
    get(Pid) ->
        Pid ! {get, self()},
        receive
            {count, Count} -> Count
        after 1000 ->
            {error, timeout}
        end.
    
    loop(Count) ->
        receive
            increment ->
                loop(Count + 1);
            {get, Pid} ->
                Pid ! {count, Count},
                loop(Count)
        end.
    
    %% Better approach: Use ETS for shared state
    -module(counter).
    -export([start/0, increment/0, get/0]).
    
    -define(TABLE, counter_table).
    
    start() ->
        ets:new(?TABLE, [named_table, public]),
        ets:insert(?TABLE, {count, 0}),
        ok.
    
    increment() ->
        ets:update_counter(?TABLE, count, 1),
        ok.
    
    get() ->
        [{count, Count}] = ets:lookup(?TABLE, count),
        Count.

    Use ETS (Erlang Term Storage) tables for shared state when appropriate, especially for read-heavy scenarios or when low-latency access is required.

    %% Anti-pattern: Poor module structure
    -module(user_system).
    -export([create_user/1, update_user/2, delete_user/1, get_user/1, list_users/0,
             authenticate_user/2, generate_password_reset_token/1]).
    
    %% Hundreds of functions in one module
    create_user(Params) -> % ...
    update_user(Id, Params) -> % ...
    delete_user(Id) -> % ...
    get_user(Id) -> % ...
    list_users() -> % ...
    authenticate_user(Email, Password) -> % ...
    generate_password_reset_token(Email) -> % ...
    %% And many more...
    
    %% Better approach: Proper module organization
    %% users.erl
    -module(users).
    -export([create/1, update/2, delete/1, get/1, list/0]).
    
    create(Params) -> % ...
    update(Id, Params) -> % ...
    delete(Id) -> % ...
    get(Id) -> % ...
    list() -> % ...
    
    %% users_auth.erl
    -module(users_auth).
    -export([authenticate/2, generate_password_reset_token/1]).
    
    authenticate(Email, Password) -> % ...
    generate_password_reset_token(Email) -> % ...

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

    %% Anti-pattern: Poor or no documentation
    -module(user_api).
    -export([create/1]).
    
    create(Params) ->
        %% Implementation...
        {ok, User}.
    
    %% Better approach: Proper documentation
    -module(user_api).
    -export([create/1]).
    
    %% @doc Creates a new user with the given parameters.
    %%
    %% Params is a map containing user attributes:
    %% <ul>
    %%   <li>`name': The user's full name (required)</li>
    %%   <li>`email': The user's email address (required)</li>
    %%   <li>`age': The user's age (optional)</li>
    %% </ul>
    %%
    %% Examples:
    %% ```
    %% {ok, User} = user_api:create(#{name => "John Doe", email => "john@example.com"}).
    %% {error, email_required} = user_api:create(#{name => "John Doe"}).
    %% '''
    %%
    %% @returns `{ok, User}' if the user was created successfully, or
    %%          `{error, Reason}' if there was an error.
    -spec create(map()) -> {ok, map()} | {error, atom()}.
    create(Params) ->
        %% Implementation...
        {ok, User}.

    Write comprehensive documentation for your modules and functions using EDoc comments. Include examples, parameter descriptions, and return value information.

    %% Anti-pattern: No type specs
    -module(calculator).
    -export([add/2, divide/2]).
    
    add(A, B) ->
        A + B.
    
    divide(A, B) ->
        A / B.
    
    %% Better approach: Use type specs
    -module(calculator).
    -export([add/2, divide/2]).
    
    -spec add(number(), number()) -> number().
    add(A, B) ->
        A + B.
    
    -spec divide(number(), number()) -> float() | {error, division_by_zero}.
    divide(A, B) when B /= 0 ->
        A / B;
    divide(_A, 0) ->
        {error, division_by_zero}.

    Use Dialyzer and type specifications to catch type errors at compile time and improve code documentation.

    %% Anti-pattern: Ad-hoc application structure
    %% Just a collection of modules without proper OTP structure
    
    %% Better approach: Proper OTP application structure
    %% my_app.app.src
    {application, my_app, [
        {description, "My Application"},
        {vsn, "0.1.0"},
        {registered, []},
        {mod, {my_app_app, []}},
        {applications, [kernel, stdlib]},
        {env, []},
        {modules, []}
    ]}.
    
    %% my_app_app.erl
    -module(my_app_app).
    -behaviour(application).
    -export([start/2, stop/1]).
    
    start(_StartType, _StartArgs) ->
        my_app_sup:start_link().
    
    stop(_State) ->
        ok.
    
    %% my_app_sup.erl
    -module(my_app_sup).
    -behaviour(supervisor).
    -export([start_link/0, init/1]).
    
    start_link() ->
        supervisor:start_link({local, ?MODULE}, ?MODULE, []).
    
    init([]) ->
        SupFlags = #{strategy => one_for_one, intensity => 10, period => 60},
        ChildSpecs = [
            %% Child specifications
        ],
        {ok, {SupFlags, ChildSpecs}}.

    Follow the OTP application structure with proper application, supervisor, and worker modules.

    %% Anti-pattern: Not registering important processes
    start() ->
        Pid = spawn(fun() -> loop([]) end),
        Pid. %% Caller needs to keep track of the Pid
    
    %% Usage
    use_service() ->
        Pid = start(),
        Pid ! {get_data, self()},
        receive
            {data, Data} -> Data
        end.
    
    %% Better approach: Register important processes
    start() ->
        Pid = spawn(fun() -> loop([]) end),
        register(my_service, Pid),
        {ok, Pid}.
    
    %% Usage
    use_service() ->
        my_service ! {get_data, self()},
        receive
            {data, Data} -> Data
        end.

    Register important long-lived processes with names for easier access. This is especially important for system processes that need to be accessed from multiple places.

    %% Anti-pattern: Ambiguous message patterns
    loop(State) ->
        receive
            {get, Key} ->
                Value = maps:get(Key, State, undefined),
                %% Who sent this message? No way to reply
                loop(State);
            {set, Key, Value} ->
                loop(maps:put(Key, Value, State))
        end.
    
    %% Better approach: Include sender in messages
    loop(State) ->
        receive
            {get, Key, From} ->
                Value = maps:get(Key, State, undefined),
                From ! {response, Value},
                loop(State);
            {set, Key, Value, From} ->
                From ! ok,
                loop(maps:put(Key, Value, State))
        end.

    Include the sender’s PID in messages that require a response. This allows the receiving process to reply to the correct sender.

    %% Anti-pattern: Using lists:foreach for transformations
    process_items(Items) ->
        Results = [],
        lists:foreach(fun(Item) ->
            Result = process_item(Item),
            Results = [Result | Results] %% This doesn't work as expected!
        end, Items),
        lists:reverse(Results).
    
    %% Better approach: Use list comprehensions or lists:map
    process_items(Items) ->
        [process_item(Item) || Item <- Items].
    
    %% Or
    process_items(Items) ->
        lists:map(fun process_item/1, Items).

    Use list comprehensions or lists:map for transforming lists. lists:foreach is only for side effects and doesn’t return a useful value.

    %% Anti-pattern: Manual testing
    test() ->
        5 = add(2, 3),
        io:format("Test passed!~n").
    
    %% Better approach: Use EUnit
    -module(calculator_tests).
    -include_lib("eunit/include/eunit.hrl").
    
    add_test() ->
        ?assertEqual(5, calculator:add(2, 3)),
        ?assertEqual(0, calculator:add(-2, 2)),
        ?assertEqual(-5, calculator:add(-2, -3)).
    
    divide_test() ->
        ?assertEqual(2.5, calculator:divide(5, 2)),
        ?assertEqual({error, division_by_zero}, calculator:divide(5, 0)).

    Use proper testing frameworks like EUnit or Common Test instead of writing manual test code.

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