name: elixir-idioms description: Elixir 惯用模式、BEAM 架构、OTP 模式和最佳实践。在编写 Elixir 代码时加载,以确保符合惯用风格。 user-invocable: false
Elixir 惯用语
编写符合 BEAM 感知模式的惯用 Elixir 代码的参考。
铁律 — 永不违反这些
- 没有运行时原因,就不创建进程 — 进程建模并发、状态、隔离—而不是代码结构
- 消息会被复制 — 保持消息小(除了大于 64 字节的二进制数据)
- 守卫使用
and/or/not— 守卫中不要使用短路运算符(守卫需要布尔操作数) - 外部数据使用变化集 — 用户输入使用
cast/4,内部使用change/2 - 仅对外部代码使用 rescue — 永远不要用 rescue 进行控制流
- 不要动态创建原子 —
String.to_atom(user_input)会导致内存泄漏(原子不被垃圾回收) - 编译时文件使用
@external_resource— 在编译时读取文件的模块必须声明@external_resource - 监督所有长期存活的进程 — 在生产中永远不要裸使用
GenServer.start_link/Agent.start_link。使用监督树 - 包装第三方库 API — 总是将外部依赖包装在项目拥有的模块后面。便于在不影响调用者的情况下替换
BEAM 架构(为什么 Elixir 这样工作)
- 进程很便宜(2.6KB) — 为了并发/隔离,可以随意生成
- 完全的内存隔离 — 没有共享状态,不需要锁
- 消息会被复制(除了大于 64 字节的二进制数据) — 保持消息小
- 每个进程的垃圾回收 — 没有全局 GC 暂停
- “让它崩溃” — 监督器会重启到已知良好状态
核心原则
- 优先使用模式匹配而非条件判断 — 函数头优先,然后
case,然后cond - 标记元组用于预期失败 —
{:ok, _}/{:error, _}用于预期错误,raise 用于 bug - 管道运算符用于数据转换 — 以数据开始,永远不要管道单个调用
- 让它崩溃 — 处理预期错误,对意外的崩溃
- 明确优于隐晦 — 明确表达意图
快速决策树
控制流
需要模式匹配? → case(或函数头)
多个操作? → with
布尔条件? → cond(多个)或 if(单个)
错误处理
预期失败? → {:ok, _}/{:error, _} 元组
意外/bug? → raise 异常(让监督器处理)
外部库? → rescue(仅此处!)
OTP
需要状态?
├─ 否 → 普通函数
├─ 简单获取/更新 → Agent 或 ETS
├─ 复杂消息/超时 → GenServer
└─ 一次性异步 → Task
快速模式
# 函数头中的模式匹配
def process(%{status: :active} = user), do: activate(user)
def process(%{status: :inactive} = user), do: deactivate(user)
# with 用于快乐路径
with {:ok, user} <- get_user(id),
{:ok, order} <- create_order(user) do
{:ok, order}
end
# Task 用于异步
Task.Supervisor.async_nolink(TaskSup, fn -> work() end)
|> Task.yield(5000) || Task.shutdown(task)
常见陷阱
| 错误 | 正确 |
|---|---|
length(list) == 0 |
list == [] 或 Enum.empty?(list) |
list ++ [item] |
[item | list] |> Enum.reverse() |
String.to_atom(input) |
String.to_existing_atom(input) |
spawn(fn -> log(conn) end) |
ip = conn.ip; spawn(fn -> log(ip) end) |
unless condition |
if !condition(unless 在 1.18 中已弃用) |
参考文献
详细模式,参见:
references/pattern-matching.md- 模式匹配、守卫、二进制匹配references/otp-patterns.md- GenServer、Supervisor、Task、Registryreferences/error-handling.md- 标记元组、rescue、withreferences/with-and-pipes.md- 何时使用with和|>(惯用模式)references/troubleshooting.md- 生产 BEAM 调试(内存、性能、崩溃)references/anti-patterns.md- 常见错误和修复references/mix-tasks.md- Mix 任务命名、选项解析、shell 输出references/elixir-118-features.md- Duration 模块、dbg 改进(1.18+)