Установка и инструментарий Elixir
Хороший инструментарий в Elixir — это не «дополнительная опция», а часть культуры. mix собирает и тестирует, hex тянет зависимости, mix format устраняет споры о стиле, Credo ловит запахи, Dialyzer находит несоответствия типов, ElixirLS даёт автодополнение в редакторе. Настроим всё это правильно с первого дня — потом эти привычки сэкономят вам недели.
Что именно вы ставите
Elixir работает поверх Erlang/OTP, поэтому вам нужны обе платформы: сам Erlang/OTP (виртуальная машина BEAM) и Elixir. Версии связаны: конкретный Elixir требует минимальной версии OTP. Управлять этим руками — боль, поэтому используем менеджер версий.
Установка через asdf (рекомендуемый путь)
asdf позволяет держать разные версии Erlang и Elixir на проект — критично, когда вы ведёте несколько сервисов.
# Ставим сам asdf (пример для Linux/macOS через git)
git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0
echo '. "$HOME/.asdf/asdf.sh"' >> ~/.zshrc # для bash — ~/.bashrc
exec $SHELL
# Добавляем плагины
asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git
asdf plugin add elixir https://github.com/asdf-vm/asdf-elixir.git
# Ставим конкретные версии (сначала Erlang, потом Elixir)
asdf install erlang 27.0
asdf install elixir 1.17.2-otp-27 # суффикс -otp-27 важен: сборка под OTP 27
# Фиксируем глобально
asdf global erlang 27.0
asdf global elixir 1.17.2-otp-27
Версии проекта фиксируются файлом .tool-versions в корне — коммитьте его, чтобы у всей команды и CI было одинаковое окружение:
erlang 27.0
elixir 1.17.2-otp-27
Проверка:
elixir --version
# Erlang/OTP 27 ... Elixir 1.17.2 (compiled with Erlang/OTP 27)
Альтернативы: на macOS многие используют
brew install elixir(быстро, но одна версия), в 2024+ появился официальный установщик elixir-lang.org/install.html. Для командной работы asdf всё же удобнее из-за.tool-versions.
IEx — интерактивная оболочка
iex — ваш главный инструмент обучения и отладки. Запускается сам по себе или в контексте проекта:
iex # чистый REPL
iex -S mix # REPL с загруженным проектом (все модули доступны)
Внутри полезны: h Enum.map (документация), i value (инспекция значения), recompile() (пересобрать проект без выхода), c "file.exs" (скомпилировать файл). REPL-driven разработка — норма: вы проверяете гипотезы в IEx, потом переносите в код.
Mix — сборка, задачи, окружения
Mix — это build-tool и task runner в одном флаконе (аналог cargo/npm/make). Создаём проект:
mix new my_app # обычное приложение
mix new my_app --sup # приложение с деревом супервизии (для боевых сервисов — почти всегда так)
cd my_app
Полезные команды:
mix deps.get # скачать зависимости
mix compile # скомпилировать
mix test # прогнать тесты
mix run -e "IO.puts 42" # выполнить выражение
mix help # список всех задач
Окружения (MIX_ENV): по умолчанию три — :dev, :test, :prod. Это влияет на конфигурацию и на то, какие зависимости подключены (only: :test и т.п.). Задаётся переменной окружения:
MIX_ENV=prod mix compile
Свои Mix-задачи пишутся как модули Mix.Tasks.MyTask — так оформляют миграции данных, генераторы и рутину.
Hex — менеджер пакетов
Hex — центральный репозиторий пакетов (как npm/PyPI). Зависимости объявляются в mix.exs:
defmodule MyApp.MixProject do
use Mix.Project
def project do
[
app: :my_app,
version: "0.1.0",
elixir: "~> 1.17",
elixirc_paths: elixirc_paths(Mix.env()),
deps: deps()
]
end
# Приложение и его OTP-зависимости, стартующие с системой
def application do
[extra_applications: [:logger], mod: {MyApp.Application, []}]
end
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
defp deps do
[
{:jason, "~> 1.4"}, # JSON
{:ecto_sql, "~> 3.11"}, # БД
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.4", only: [:dev], runtime: false},
{:stream_data, "~> 1.0", only: [:test]}
]
end
end
~> 1.4 — это семантический диапазон: «>= 1.4.0 и < 2.0.0». Точные версии фиксируются в mix.lock (коммитьте его!). Обновление — mix deps.update <pkg> или mix deps.update --all.
mix format — форматтер, отменяющий споры о стиле
В Elixir есть официальный форматтер. Никаких дискуссий про табы и переносы — есть один канонический стиль. Настройка в .formatter.exs:
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
line_length: 98
]
Команды:
mix format # отформатировать всё
mix format --check-formatted # упасть, если не отформатировано (для CI)
Практика: включите format-on-save в редакторе и добавьте mix format --check-formatted в CI. Форматированный код — обязательное условие ревью.
Credo — линтер и советчик по стилю
Credo ловит то, что форматтер не видит: сложные функции, потенциальные баги, антипаттерны, «код с запашком».
mix credo # анализ
mix credo --strict # строгий режим (рекомендуется в CI)
mix credo suggest # с предложениями по исправлению
Конфиг генерируется в .credo.exs (mix credo.gen.config) — там включают/выключают правила под команду.
Dialyzer и typespecs — статический анализ несоответствий
Elixir динамически типизирован, но у него есть опциональные аннотации типов (@spec) и инструмент Dialyzer (через обёртку dialyxir), который методом «success typing» находит несовместимости: функцию, которая никогда не может вернуть то, что от неё ждут, недостижимый код, неверные контракты.
@spec add(integer(), integer()) :: integer()
def add(a, b), do: a + b
@type user :: %{name: String.t(), age: non_neg_integer()}
@spec greet(user()) :: String.t()
def greet(%{name: name}), do: "Привет, #{name}"
mix dialyzer # первый запуск строит PLT (кэш типов OTP) — это долго, потом быстро
Dialyzer не находит все ошибки типов (он консервативен: сообщает только о точно невозможном), но пойманные им проблемы — почти всегда реальные баги. Держите PLT в кэше CI.
В экосистеме появляется постепенная типизация в самом компиляторе (проект «set-theoretic types», Elixir 1.17+). Следите за dashbit.co/blog — это меняет ландшафт статических проверок.
LSP / IDE
- VS Code: расширение ElixirLS — автодополнение, переход к определению, инлайн-Dialyzer, форматирование. Появляется и более новый Lexical, и Expert — можно попробовать, если ElixirLS тормозит.
- Neovim:
elixir-tools.nvimили ElixirLS черезnvim-lspconfig. - Другие: плагины есть для IntelliJ, Emacs.
Минимальный набор ожиданий от LSP: go-to-definition, hover-документация, автодополнение функций модуля, подсветка ошибок компиляции.
Структура проекта
mix new my_app --sup создаёт каноничный layout:
my_app/
├── .formatter.exs # правила форматтера
├── .tool-versions # версии Erlang/Elixir (asdf)
├── mix.exs # манифест проекта: зависимости, приложение
├── mix.lock # зафиксированные версии (коммитить!)
├── config/
│ ├── config.exs # общая конфигурация (compile-time)
│ ├── dev.exs # переопределения для dev
│ ├── test.exs
│ ├── prod.exs
│ └── runtime.exs # runtime-конфигурация (читается при старте релиза!)
├── lib/
│ ├── my_app.ex # публичный «фасад» приложения
│ ├── my_app/
│ │ └── application.ex # дерево супервизии — точка входа OTP-приложения
├── test/
│ ├── test_helper.exs # ExUnit.start()
│ └── my_app_test.exs
Ключевые принципы организации lib/:
- Один модуль — один файл, путь файла отражает вложенность модуля:
MyApp.Accounts.User→lib/my_app/accounts/user.ex. - Бизнес-логику группируйте по контекстам (
MyApp.Accounts,MyApp.Billing) — об этом в статье про архитектуру. application.exописывает, что стартует и под каким супервизором — сердце OTP-приложения.
# lib/my_app/application.ex
defmodule MyApp.Application do
use Application
@impl true
def start(_type, _args) do
children = [
# процессы, которые стартуют вместе с приложением
# MyApp.Repo,
# {Phoenix.PubSub, name: MyApp.PubSub},
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
config.exs vs runtime.exs — важное различие
config/config.exsиconfig/{dev,test,prod}.exsчитаются на этапе компиляции. Секреты сюда класть нельзя — они «впекутся» в артефакт.config/runtime.exsчитается при старте приложения/релиза. Именно здесь читают переменные окружения (System.get_env/1) — URL базы, секретные ключи. Это правильное место для всего, что зависит от среды исполнения.
# config/runtime.exs
import Config
if config_env() == :prod do
config :my_app, MyApp.Repo,
url: System.fetch_env!("DATABASE_URL"),
pool_size: String.to_integer(System.get_env("POOL_SIZE", "10"))
end
Подробнее о конфигурации и релизах — в статье Деплой и наблюдаемость.
Чек-лист «здорового» проекта
-
.tool-versionsзафиксирован и в CI, и локально. -
mix format --check-formattedв CI. -
mix credo --strictв CI. -
mix dialyzerс кэшированием PLT в CI. -
mix test --coverи порог покрытия. - Секреты только в
runtime.exsчерез переменные окружения.
Источники
- Установка: elixir-lang.org/install.html
- Mix и Hex: hexdocs.pm/mix, hex.pm/docs
- Credo: hexdocs.pm/credo, Dialyxir: hexdocs.pm/dialyxir
Что дальше
Основы языка — синтаксис, типы, структуры данных и паттерн-матчинг.